ฉันจะค้นหาไดเรกทอรีที่ขาดหายไปเป็นครั้งแรกในเส้นทางที่ยาวได้อย่างไร


14

ลองนึกภาพฉันมีเส้นทางที่ไม่มีอยู่:

$ ls /foo/bar/baz/hello/world
ls: cannot access /foo/bar/baz/hello/world: No such file or directory

แต่ขอบอกว่า/foo/bar ไม่มีอยู่ มีวิธีที่รวดเร็วสำหรับฉันในการพิจารณาว่าbazเป็นจุดแตกหักในเส้นทางหรือไม่

ฉันกำลังใช้ Bash


access(2)ไม่ละเอียดมากดังนั้นวิธีการแก้ปัญหามักจะเกี่ยวข้องกับการเขียนสิ่งที่จะย้ำและทดสอบแต่ละองค์ประกอบในการเปิดเส้นทาง ...
thrig

คำตอบ:


5

ด้วยชื่อพา ธ แบบบัญญัติเช่นของคุณสิ่งนี้จะใช้งานได้:

set -f --; IFS=/
for p in $pathname
do    [ -e "$*/$p" ] || break
      set -- "$@" "$p"
done; printf %s\\n "$*"

ซึ่งจะพิมพ์ผ่านองค์ประกอบที่มีอยู่ / เข้าถึงได้ล่าสุดของ$pathnameและทำให้แต่ละองค์ประกอบแยกจากกันในอาร์เรย์ ARG องค์ประกอบที่ไม่มีตัวตนครั้งแรกไม่ถูกพิมพ์ $pแต่มันจะถูกบันทึกไว้ใน

คุณอาจเข้าหามันตรงกันข้าม:

until cd -- "$path" && cd -
do    case   $path  in
      (*[!/]/*)
              path="${path%/*}"
;;    (*)   ! break
      esac
done  2>/dev/null   && cd -

ที่จะกลับมาอย่างเหมาะสมหรือจะลง$pathตามที่ต้องการ มันปฏิเสธที่จะพยายามเปลี่ยนแปลง/แต่ถ้าประสบความสำเร็จจะพิมพ์ทั้งไดเรกทอรีการทำงานปัจจุบันของคุณและไดเรกทอรีที่มันเปลี่ยนเป็น stdout ปัจจุบันของคุณ$PWDจะถูกใส่$OLDPWDเช่นกัน


ชัดเจน: for p in $pathnameจะอยู่ภายใต้ "การแยกคำ" และ "การขยายชื่อพา ธ "

1
@BinaryZebra - $IFSใช่มันถูกแบ่งออกบน มันทำงานอย่างไร มันไม่ได้เป็นเรื่องการขยายตัวของพา ธ ยกเว้นว่าตัวแปรที่นี่เรียกว่ามีการขยายไปยังอาร์เรย์ของส่วนประกอบเส้นทางเป็นแยกบน$pathname $IFS
mikeserv

คุณกำลังหลีกเลี่ยง "การขยายชื่อพา ธ " บน var $pathname โดยใช้set -f"ซึ่งไม่ได้กลับไปเป็นค่าเริ่มต้น

@BinaryZebra - ฉันไม่ได้หลีกเลี่ยงอะไร ฉันกำลังระบุสภาพแวดล้อมที่ฉันต้องการเพื่อที่จะทำสิ่งนี้อย่างสมบูรณ์ นั่นคือทั้งหมด ไม่ - มันไม่กลับไปเป็นค่าเริ่มต้นและจะไม่ติดตั้งตัวเองบนคอมพิวเตอร์ของผู้ถามหรือทำให้ฉันกินอาหารเช้า
mikeserv

1
@BinaryZebra - หากคุณเป็นโปรแกรมเมอร์ที่ดีคุณควรพบว่าตัวเองกังวลเกี่ยวกับผลกระทบที่ไม่จำเป็น / การกู้คืนสภาพแวดล้อมในการดำเนินการของคุณคุณอาจสนใจnsซึ่งหากใช้ที่นี่
mikeserv

12

หนึ่งในการใช้ประโยชน์ที่ฉันโปรดปรานคือnameiส่วนหนึ่งutil-linuxและโดยทั่วไปจะปรากฏเฉพาะบน Linux:

$ namei /usr/share/foo/bar
f: /usr/share/foo/bar
 d /
 d usr
 d share
   foo - No such file or directory

แต่ผลลัพธ์ของมันไม่สามารถแยกวิเคราะห์ได้มาก ดังนั้นหากคุณต้องการชี้ให้เห็นสิ่งที่ขาดหายไปnameiอาจเป็นประโยชน์

มันมีประโยชน์สำหรับการแก้ไขปัญหาทั่วไปในการเข้าถึงพา ธ เนื่องจากคุณสามารถระบุว่าส่วนประกอบนั้นเป็นลิงค์หรือจุดเชื่อมต่อรวมถึงการอนุญาต:

$ ln -sf /usr/foo/bar /tmp/
$ namei -lx /tmp/bar
f: /tmp/bar
Drwxr-xr-x root    root    /
Drwxrwxrwt root    root    tmp
lrwxrwxrwx muru    muru    bar -> /usr/foo/bar
Drwxr-xr-x root    root      /
drwxr-xr-x root    root      usr
                             foo - No such file or directory

เมืองหลวงDหมายถึงจุดที่เมานท์


@ StéphaneChazelasฉันหวังว่ารายการเทียบเท่าอาจมีอยู่ในระบบอื่น ไม่มีอะไรสำหรับ BSDs?
muru

nameiงานสำหรับฉันตราบเท่าที่ฟีดมันเส้นทางที่มีอยู่ namei: failed to stat: /usr/share/foo/bar: No such file or directoryแต่เมื่อฉันให้มันเป็นหนึ่งที่ไม่ได้ฉันจะได้รับ
kuzzooroo

@kuzzooroo namei เวอร์ชันไหน?
muru

รุ่น namei ของฉันไม่ได้ให้รุ่นในความช่วยเหลือหรือหน้าคนของมันและไม่สนับสนุนการตั้งค่าสถานะให้ข้อมูลรุ่น ฉันสามารถตรวจสอบได้ว่ามันมาจากแพ็คเกจ linux-utils 2.16-1ubuntu5 แต่ไม่สามารถเข้าใจได้ว่าอะไรที่มีความหมายกับรุ่นของ namei
kuzzooroo

@kuzzooroo ตั้งแต่แม้Ubuntu 12.04 จะมี 2.20.1คุณต้องอยู่ใน Ubuntu รุ่นเก่ามากอย่างแน่นอน 10.04?
muru

1

บางสิ่งเช่นนี้ (การบัญชีสำหรับชื่อพา ธ ที่มีช่องว่างภายใน):

#!/bin/sh
explain() {
    if [ -d "$1" ]
    then
        printf "\t%s: is a directory\n" "$1"
    elif [ -e "$1" ]
    then
        printf "\t%s: is not a directory\n" "$1"
    else
        printf "\t%s: does not exist\n" "$1"
    fi
}

for item in "$@"
do
    last=
    test="$item"
    printf "testing: '%s'\n" "$item"
    while [ ! -d "$test" ]
    do
        last="$test"
        test=$(dirname "$test")
        [ -z "$test" ] && break
    done
    if [ -n "$last" ]
    then
        explain "$test"
        explain "$last"
    else
        printf "\t%s: ok\n" "$item"
    fi
done

มันถือว่าอาร์กิวเมนต์มีขึ้นเพื่อเป็นไดเรกทอรี (หรือ symlink ไปยังไดเรกทอรี)
Stéphane Chazelas

1
ถ้าคุณเพียงแค่เปลือกของคุณก็จะเขียนถึงบางสิ่งบางอย่างเช่นcd not_a_directory stderr cd:cd:6: no such file or directory: not_a_directoryที่จริงแล้วเชลล์ของผู้ใช้จะทำในรูปแบบที่ผู้ใช้อาจคุ้นเคยอยู่แล้ว มันเกือบจะง่ายกว่าและดีกว่าในท้ายที่สุดแล้วในการทำสิ่งต่างๆและให้เชลล์จัดการการรายงานตามที่จำเป็น ปรัชญาประเภทนั้นต้องการความใส่ใจอย่างเข้มงวดในการคืนค่าและการส่งเสริม / การสงวนรักษาไว้เหมือนกัน
mikeserv

1

เพียงวิธีการแก้ปัญหาทางเลือกสำหรับทุบตีสมมติว่าเส้นทางเป็นเส้นทางที่แน่นอน (เริ่มต้นด้วย /):

#!/bin/bash

pathname="$1"

IFS='/' read -r -a p <<<"${pathname#/}"

pa=""    max="${#p[@]}"    i=0
while (( i<"$max" )); do
      pa="$pa/${p[i++]}"
      if     [[ ! -e $pa ]]; then
             printf 'failed at: \t"%s"\t"%s"\n' "${pa##*/}" "${pa}"
             break
      fi
done

$ ./script "/foo/ba r/baz/hello/world"
failed at:      "hello"        "/foo/ba r/baz/hello"

สิ่งนี้ใช้ได้สำหรับฉัน ฉันต้องการพา ธ ที่ถูกต้องครั้งสุดท้ายในสตริงพา ธ ดังนั้นฉันจึงบันทึกpaเป็นpa_prevบรรทัดแรกของลูป while (ก่อนที่จะเพิ่มขึ้น) เมื่อการทดสอบสำหรับ "pa" นั้นล้มเหลวpa_prevมีไดเรกทอรีที่มีอยู่ล่าสุดในเส้นทางที่กำหนด
วิลล์

0
(( dirct=$(echo ${dir}|tr "/" " "|wc -w)+1 ))
i=2
while [ ${i} -le ${dirct} ]
do
  sdir=$(echo ${dir}|cut -d/ -f1,${i})
  if [ ! -d ${sdir} ]
  then
    echo "Path is broken at ${sdir}"
  fi
  (( i++ ))
done

มันไม่ง่าย แต่คุณสามารถใส่มันลงในสคริปต์ทำให้มันสามารถใช้งานได้และติดมันไว้ที่ใดที่หนึ่งในเส้นทางของคุณถ้าคุณจะใช้มันบ่อยๆ

Caveat emptor: หากชื่อไดเรกทอรีของคุณในทุกระดับมีspaceตัวอักษรสิ่งนี้จะไม่ทำงาน


รหัสทุบตีนี้หรือไม่ ฉันต้องสลับใน $ {dir} กำลังจะว่างเปล่าดังนั้นฉันจึงสลับเป็น $ 1 แต่ตอนนี้ sdir กำลังจะว่างเปล่าสำหรับฉัน
kuzzooroo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.