ดำเนินการในทุกไดเรกทอรีย่อยโดยใช้ Bash


196

ฉันทำงานกับสคริปต์ที่ต้องการดำเนินการในทุกไดเรกทอรีย่อยของโฟลเดอร์เฉพาะ

วิธีที่มีประสิทธิภาพที่สุดในการเขียนคืออะไร?


1
โปรดพิจารณากลับมาผ่านและประเมินคำตอบที่ถูกต้อง - คุณจะได้มีคำตอบที่ได้รับการยอมรับได้รับจำนวนมากของมุมมองแม้จะมีข้อบกพร่องที่สำคัญ (F / E วิ่งมันมากกว่าไดเรกทอรีที่มีคนก่อนหน้านี้วิ่งmkdir 'foo * bar'จะทำให้เกิดfooและbarจะได้รับการซ้ำมากกว่าแม้ว่า ไม่มีอยู่และ*จะถูกแทนที่ด้วยรายการชื่อไฟล์ทั้งหมดแม้จะไม่ใช่ไดเรกทอรี)
Charles Duffy

1
... แม้เลวร้ายก็คือถ้ามีคนวิ่งmkdir -p '/tmp/ /etc/passwd /'- ถ้ามีคนทำงานสคริปต์ต่อไปนี้การปฏิบัตินี้ใน/tmpการพูด, /etc/passwdหาไดเรกทอรีที่จะลบพวกเขาจะจบลงด้วยการลบ
Charles Duffy

คำตอบ:


179
for D in `find . -type d`
do
    //Do whatever you need with D
done

81
สิ่งนี้จะทำลายช่องว่างสีขาว
ghostdog74

2
นอกจากนี้โปรดทราบว่าคุณต้องปรับแต่งfindparams หากคุณต้องการพฤติกรรมแบบเรียกซ้ำหรือไม่เรียกซ้ำ
Chris Tonkinson

32
คำตอบดังกล่าวข้างต้นให้ฉันไดเรกทอรีตนเองเช่นกันดังนั้นต่อไปนี้จะทำงานได้ดีขึ้นเล็กน้อยสำหรับฉัน: find . -mindepth 1 -type d
jzheaux

1
@JoshC ตัวแปรทั้งสองจะแยกไดเรกทอรีที่สร้างโดยmkdir 'directory name with spaces'เป็นคำที่แยกกันสี่คำ
Charles Duffy

4
ฉันต้องการเพิ่ม-mindepth 1 -maxdepth 1หรือลึกเกินไป
ScrappyDev

284

เวอร์ชันที่หลีกเลี่ยงการสร้างกระบวนการย่อย:

for D in *; do
    if [ -d "${D}" ]; then
        echo "${D}"   # your processing here
    fi
done

หรือหากการกระทำของคุณเป็นคำสั่งเดียวนี่จะกระชับมากขึ้น:

for D in *; do [ -d "${D}" ] && my_command; done

หรือรุ่นที่กระชับยิ่งขึ้น ( ขอบคุณ @enzotib ) โปรดทราบว่าในรุ่นนี้แต่ละค่าของDจะมีเครื่องหมายทับ:

for D in */; do my_command; done

49
คุณสามารถหลีกเลี่ยงifหรือ[ด้วย:for D in */; do
enzotib

2
+1 เพราะชื่อไดเรกทอรีไม่ได้เริ่มต้นด้วย ./ เมื่อเทียบกับคำตอบที่ได้รับการยอมรับ
Hayri Uğur Koltuk

3
อันนี้ถูกต้องมากถึงช่องว่างในชื่อไดเรกทอรี +1
อเล็กซ์ Reinking

5
มีปัญหาอย่างหนึ่งกับคำสั่งสุดท้าย: หากคุณอยู่ในไดเรกทอรีที่ไม่มีไดเรกทอรีย่อย มันจะส่งคืน "* /" ดังนั้นควรใช้คำสั่งที่สองfor D in *; do [ -d "${D}" ] && my_command; doneหรือการรวมกันของสองล่าสุด:for D in */; do [ -d $D ] && my_command; done
Chris Maes

5
โปรดทราบว่าคำตอบนี้จะไม่สนใจไดเรกทอรีที่ซ่อนอยู่ ต้องการรวมไดเรกทอรีที่ซ่อนอยู่ใช้แทนfor D in .* *; do for D in *; do
patryk.beza

107

วิธีแบบเรียกซ้ำที่ง่ายที่สุดคือ:

for d in */; do
    echo "$d"
done

/ในตอนท้ายบอกใช้ไดเรกทอรีเท่านั้น

ไม่จำเป็นต้องมี

  • หา
  • awk
  • ...

11
หมายเหตุ: สิ่งนี้จะไม่รวมถึงจุด dirs (ซึ่งอาจเป็นสิ่งที่ดี แต่สิ่งสำคัญที่ต้องรู้)
wisbucky

มีประโยชน์ที่จะต้องทราบว่าคุณสามารถใช้shopt -s dotglobเพื่อรวม dotfiles / dotdirs เมื่อขยายสัญลักษณ์ ดูเพิ่มเติมที่: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Steen Schütt

ฉันคิดว่าคุณหมาย/*แทนการ*/กับ/ตัวแทนของเส้นทางที่คุณต้องการใช้
Brōtsyorfuzthrāx

2
@Shule /*จะเป็นเส้นทางที่แน่นอนในขณะที่*/จะรวมไดเรกทอรีย่อยจากที่ตั้งปัจจุบัน
Dan G

3
คำแนะนำที่เป็นประโยชน์: หากคุณต้องการตัดส่วนต่อท้าย '/' จาก $ d ให้ใช้${d%/*}
Hafthor

18

ใช้findคำสั่ง

ใน GNU findคุณสามารถใช้-execdirพารามิเตอร์:

find . -type d -execdir realpath "{}" ';'

หรือโดยใช้-execพารามิเตอร์:

find . -type d -exec sh -c 'cd -P "$0" && pwd -P' {} \;

หรือด้วยxargsคำสั่ง:

find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'

หรือใช้สำหรับวง:

for d in */; { echo "$d"; }

สำหรับ recursivity ลองขยาย globbing ( **/) แทน (เปิดใช้งานโดย: shopt -s extglob)


สำหรับตัวอย่างเพิ่มเติมดู: วิธีไปที่แต่ละไดเรกทอรีและดำเนินการคำสั่ง? ที่ดังนั้น


1
-exec {} +เป็น POSIX-ระบุเป็นอีกตัวเลือกตามกฎหมายและจะเรียกน้อยกว่าเปลือกหอย-exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} + -exec sh -c '...' {} \;
Charles Duffy

13

หนึ่งสมุทรที่มีประโยชน์

for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A

find * -type d ### Option B

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

# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real    0m0.327s
user    0m0.084s
sys     0m0.236s

# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real    0m0.787s
user    0m0.484s
sys     0m0.308s

10

find . -type d -print0 | xargs -0 -n 1 my_command


ฉันสามารถป้อนค่าส่งคืนของการค้นหานั้นลงในลูป for ได้หรือไม่ นี่เป็นส่วนหนึ่งของสคริปต์ที่มีขนาดใหญ่กว่า ...
mikewilliamson

@ ไมค์ไม่น่าเป็นไปได้ $? อาจจะได้รับสถานะของการค้นหาหรือคำสั่ง xargs my_commandมากกว่า
พอลทอมบลิน

7

สิ่งนี้จะสร้าง subshell (ซึ่งหมายความว่าค่าตัวแปรจะหายไปเมื่อwhileลูปออก):

find . -type d | while read -r dir
do
    something
done

สิ่งนี้จะไม่:

while read -r dir
do
    something
done < <(find . -type d)

อาจใช้งานได้หากมีช่องว่างในชื่อไดเรกทอรี


เพื่อการจัดการชื่อไฟล์ที่แปลกยิ่งขึ้น (รวมถึงชื่อที่ลงท้ายด้วย whitespace และ / หรือรวมถึง linefeeds) ให้ใช้find ... -print0และwhile IFS="" read -r -d $'\000' dir
Gordon Davisson

@GordonDavisson, ... แน่นอนฉันยังยืนยันว่า-d ''มีความเข้าใจผิดเกี่ยวกับไวยากรณ์และความสามารถในการทุบตีน้อยลงเนื่องจาก-d $'\000'นัย (เท็จ) ที่$'\000'แตกต่างจาก''- จริง ๆ แล้วใคร ๆ ก็สามารถเข้าใจได้อย่างง่ายดาย นั่นคือทุบตีสนับสนุนสตริงสไตล์ Pascal (ระบุความยาว, สามารถมีตัวอักษร NUL) มากกว่า C สตริง (คั่นด้วย NUL, ไม่สามารถมี NUL)
Charles Duffy

5

คุณสามารถลอง:

#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/

for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
  cd "$f"
  <your job here>
done

หรือคล้ายกัน ...

คำอธิบาย:

find . -maxdepth 1 -mindepth 1 -type d: ค้นหาเฉพาะไดเรกทอรีที่มีความลึกแบบเรียกซ้ำสูงสุด 1 (เฉพาะไดเรกทอรีย่อยของ $ 1) และความลึกขั้นต่ำ 1 (ไม่รวมโฟลเดอร์ปัจจุบัน.)


นี่คือ buggy - ลองใช้ชื่อไดเรกทอรีที่มีช่องว่าง ดูBashPitfalls # 1และDontReadLinesWithFor
Charles Duffy

ชื่อไดเรกทอรีที่มีช่องว่างอยู่ในเครื่องหมายคำพูดดังนั้นจึงใช้งานได้และ OP จะไม่พยายามอ่านบรรทัดจากไฟล์
Henry Dobson

cd "$f"มันทำงานอยู่ใน มันไม่ทำงานเมื่อผลลัพธ์จากfindการแยกสตริงดังนั้นคุณจะมีส่วนแยกของชื่อเป็นค่าที่แยกจากกัน$fทำให้คุณทำได้ดีหรือไม่อ้างคำพูดใน$fการขยายตัว
Charles Duffy

ฉันไม่ได้บอกว่าพวกเขากำลังพยายามอ่านบรรทัดจากไฟล์ findเอาท์พุทของเป็นเชิงเส้น (หนึ่งชื่อในบรรทัดในทางทฤษฎี - แต่ดูด้านล่าง) กับการ-printกระทำเริ่มต้น
Charles Duffy

Line-oriented output เนื่องจากfind -printไม่ใช่วิธีที่ปลอดภัยในการส่งชื่อไฟล์โดยพลการเนื่องจากสามารถเรียกใช้บางสิ่งmkdir -p $'foo\n/etc/passwd\nbar'และรับไดเรกทอรีที่มี/etc/passwdบรรทัดแยกต่างหากในชื่อ การจัดการชื่อจากไฟล์ใน/uploadหรือ/tmpไดเรกทอรีโดยไม่ต้องดูแลเป็นวิธีที่ดีในการรับสิทธิพิเศษในการเพิ่มการโจมตี
Charles Duffy

4

คำตอบที่ยอมรับจะแตกในช่องว่างสีขาวถ้าชื่อไดเรกทอรีมีและไวยากรณ์ที่ต้องการ$()สำหรับ bash / ksh ใช้GNU find -exec ตัวเลือกด้วย+;เช่น

find .... -exec mycommand +; #this is same as passing to xargs

หรือใช้ลูปสักครู่

find .... |  while read -r D
do
  ...
done 

สิ่งที่จะถือชื่อไดเรกทอรี? เช่น chmod + x $ DIR_NAME (ใช่ฉันรู้ว่ามีตัวเลือก chmod สำหรับไดเรกทอรีเท่านั้น)
Mike Graf

มีความแตกต่างเล็กน้อยระหว่างการfind -execส่งผ่านไปยังxargs: findจะละเว้นค่าการออกของคำสั่งที่ถูกเรียกใช้งานในขณะที่xargsจะล้มเหลวในการออกที่ไม่ใช่ศูนย์ อาจจะถูกต้องขึ้นอยู่กับความต้องการของคุณ
Jason Wodicka

find ... -print0 | while IFS= read -r dปลอดภัยมากขึ้น - รองรับชื่อที่เริ่มต้นหรือสิ้นสุดในช่องว่างและชื่อที่มีตัวอักษรขึ้นบรรทัดใหม่
Charles Duffy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.