จะไปยังแต่ละไดเรกทอรีและดำเนินการคำสั่งได้อย่างไร


144

ฉันจะเขียนสคริปต์ทุบตีที่ผ่านแต่ละไดเรกทอรีภายใน parent_directory และดำเนินการคำสั่งในแต่ละไดเรกทอรีได้อย่างไร

โครงสร้างไดเรกทอรีเป็นดังนี้:

parent_directory (ชื่อสามารถเป็นอะไรก็ได้ - ไม่เป็นไปตามรูปแบบ)

  • 001 (ชื่อไดเรกทอรีตามรูปแบบนี้)
    • 0001.txt (ชื่อไฟล์เป็นไปตามรูปแบบนี้)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

ไม่ทราบจำนวนไดเรกทอรี

คำตอบ:


108

คุณสามารถทำสิ่งต่อไปนี้ได้เมื่อไดเรกทอรีปัจจุบันของคุณคือparent_directory:

for d in [0-9][0-9][0-9]
do
    ( cd "$d" && your-command-here )
done

(และ)สร้าง subshell เพื่อไดเรกทอรีปัจจุบันจะไม่มีการเปลี่ยนแปลงในสคริปต์หลัก


5
ไม่สำคัญว่าเพราะไวด์การ์ดไม่ตรงกับตัวเลขอื่น ๆ แต่ในกรณีทั่วไปคุณต้องการใส่ชื่อไดเรกทอรีในเครื่องหมายคำพูดคู่ภายในลูป cd "$d"น่าจะดีกว่าถ้ามันถ่ายโอนไปยังสถานการณ์ที่อักขระตัวแทนจะจับคู่ไฟล์ที่มีชื่อมีช่องว่างและ / หรืออักขระเมตาของเชลล์
tripleee

1
(ทุกคำตอบที่นี่ดูเหมือนจะมีข้อบกพร่องเหมือนกัน แต่มันเป็นเรื่องสำคัญมากที่สุดในด้านบนลงมติคำตอบ.)
tripleee

1
นอกจากนี้คำสั่งส่วนใหญ่ไม่สนใจว่าไดเรกทอรีใดที่คุณเรียกใช้งาน เป็นหน้าที่เทียบเท่าfor f in foo bar; do cat "$f"/*.txt; done >output cat foo/*.txt bar/*.txt >outputอย่างไรก็ตามlsเป็นหนึ่งคำสั่งที่สร้างเอาต์พุตที่แตกต่างกันเล็กน้อยขึ้นอยู่กับว่าคุณส่งผ่านอาร์กิวเมนต์อย่างไร ในทำนองเดียวกัน a grepหรือwcชื่อไฟล์ที่เกี่ยวข้องจะแตกต่างกันหากคุณเรียกใช้จากไดเรกทอรีย่อย (แต่บ่อยครั้งที่คุณต้องการหลีกเลี่ยงการเข้าไปในไดเรกทอรีย่อยอย่างแม่นยำด้วยเหตุผลนั้น)
tripleee

159

คำตอบนี้โพสต์โดยทอดด์ช่วยฉัน

find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;

\( ! -name . \) หลีกเลี่ยงการดำเนินการคำสั่งในไดเรกทอรีปัจจุบัน


4
และถ้าคุณต้องการเรียกใช้คำสั่งในบางโฟลเดอร์เท่านั้น:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
Martin K

3
ฉันต้องทำ-exec bash -c 'cd "$0" && pwd' {} \;เนื่องจากบางไดเรกทอรีมีเครื่องหมายคำพูดเดี่ยวและบางเครื่องหมายคำพูดคู่
Charlie Gorichanaz

12
คุณสามารถละเว้นไดเรกทอรีปัจจุบันได้โดยเพิ่ม-mindepth 1
mdziob

วิธีการเปลี่ยนสิ่งนี้เป็นฟังก์ชั่นยอมรับคำสั่งเช่น "git remote get-url origin` แทนpwdโดยตรงได้หรือไม่
roachsinai

disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}ไม่ทำงาน.
roachsinai

48

หากคุณใช้ GNU findคุณสามารถลองใช้-execdirพารามิเตอร์ได้เช่น:

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

หรือ (ตามความคิดเห็น @gniourf_gniourf ):

find . -type d -execdir sh -c 'printf "%s/%s\n" "$PWD" "$0"' {} \;

หมายเหตุ: คุณสามารถใช้${0#./}แทน$0การแก้ไข./ด้านหน้า

หรือตัวอย่างที่ใช้งานได้จริง:

find . -name .git -type d -execdir git pull -v ';'

หากคุณต้องการที่จะรวมไดเรกทอรีปัจจุบันมันง่ายยิ่งขึ้นโดยใช้-exec:

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

หรือใช้xargs:

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

หรือตัวอย่างที่คล้ายกันที่แนะนำโดย@gniourf_gniourf :

find . -type d -print0 | while IFS= read -r -d '' file; do
# ...
done

ตัวอย่างข้างต้นสนับสนุนไดเรกทอรีที่มีช่องว่างในชื่อ


หรือโดยการกำหนดให้เป็นอาร์เรย์ทุบตี:

dirs=($(find . -type d))
for dir in "${dirs[@]}"; do
  cd "$dir"
  echo $PWD
done

เปลี่ยน.เป็นชื่อโฟลเดอร์เฉพาะของคุณ หากคุณไม่จำเป็นต้องเรียกใช้ซ้ำคุณสามารถใช้: dirs=(*)แทน ตัวอย่างข้างต้นไม่รองรับไดเรกทอรีที่มีช่องว่างในชื่อ

ดังนั้นตามที่@gniourf_gniourfแนะนำวิธีการที่เหมาะสมเท่านั้นที่จะนำเอาท์พุทการค้นหาในอาเรย์โดยไม่ต้องใช้ลูปที่ชัดเจนจะมีอยู่ใน Bash 4.4 ด้วย:

mapfile -t -d '' dirs < <(find . -type d -print0)

หรือไม่ใช่วิธีที่แนะนำ (ซึ่งเกี่ยวข้องกับการแยกวิเคราะห์ls ):

ls -d */ | awk '{print $NF}' | xargs -n1 sh -c 'cd $0 && pwd && echo Do stuff'

ตัวอย่างข้างต้นจะไม่สนใจ dir ปัจจุบัน (ตามที่ร้องขอโดย OP) แต่มันจะแตกชื่อด้วยช่องว่าง

ดูสิ่งนี้ด้วย:


1
วิธีเดียวที่เหมาะสมที่จะนำส่งออกของfindในอาร์เรย์โดยไม่ต้องใช้ห่วงอย่างชัดเจนจะสามารถใช้ได้ในทุบตี 4.4 mapfile -t -d '' dirs < <(find . -type d -print0)ด้วย ตัวเลือกเดียวของคุณคือใช้ลูป (หรือเลื่อนการทำงานไปที่find)
gniourf_gniourf

1
ในความเป็นจริงคุณไม่จำเป็นต้องมีdirsอาร์เรย์ คุณสามารถห่วงfind's เอาต์พุต (ปลอดภัย) find . -type d -print0 | while IFS= read -r -d '' file; do ...; doneเพื่อต้องการ:
gniourf_gniourf

@gniourf_gniourf ฉันมองเห็นและพยายามค้นหา / ทำสิ่งที่ดูเรียบง่ายอยู่เสมอ เช่นฉันเป็นสคริปต์นี้และโดยใช้ bash array นั้นง่ายและอ่านง่ายสำหรับฉัน (โดยแยกลอจิกออกเป็นชิ้นเล็ก ๆ แทนที่จะใช้ไพพ์) แนะนำท่อเพิ่มเติมIFS, readจะช่วยให้การแสดงผลของความซับซ้อนเพิ่มเติม ฉันจะต้องคิดถึงมันบางทีอาจจะมีวิธีที่ง่ายกว่านี้ในการทำเช่นนั้น
kenorb

ถ้าโดยง่ายคุณหมายถึงแตกแล้วใช่มีวิธีทำง่ายขึ้น ตอนนี้ไม่ต้องกังวลสิ่งเหล่านี้IFSและread(อย่างที่คุณพูด) กลายเป็นลักษณะที่สองเมื่อคุณชินกับมัน ... พวกเขาเป็นส่วนหนึ่งของวิธีการเขียนสคริปต์ที่เป็นที่ยอมรับและมีสำนวน
gniourf_gniourf

โดยวิธีการคำสั่งแรกของคุณfind . -type d -execdir echo $(pwd)/{} ';'ไม่ได้ทำในสิ่งที่คุณต้องการ ( $(pwd)มีการขยายก่อนที่จะfindถูกดำเนินการแม้กระทั่ง) ...
gniourf_gniourf

29

xargsคุณสามารถบรรลุนี้โดยท่อแล้วใช้ จับคือคุณต้องใช้ธงซึ่งจะแทนที่สตริงย่อยในคำสั่งของคุณด้วยการทุบตีย่อยผ่านไปแต่ละ-Ixargs

ls -d */ | xargs -I {} bash -c "cd '{}' && pwd"

คุณอาจต้องการแทนที่pwdด้วยคำสั่งอะไรก็ตามที่คุณต้องการดำเนินการในแต่ละไดเรกทอรี


2
ฉันไม่รู้ว่าเพราะเหตุใดจึงไม่อัปโหลด แต่สคริปต์ที่ง่ายที่สุดที่ฉันพบจนถึงตอนนี้ ขอบคุณ
Linvi

20

หากรู้จักโฟลเดอร์บนสุดคุณสามารถเขียนดังนี้:

for dir in `ls $YOUR_TOP_LEVEL_FOLDER`;
do
    for subdir in `ls $YOUR_TOP_LEVEL_FOLDER/$dir`;
    do
      $(PLAY AS MUCH AS YOU WANT);
    done
done

เกี่ยวกับ $ (เล่นมากเท่าที่คุณต้องการ); คุณสามารถใส่รหัสได้มากเท่าที่คุณต้องการ

โปรดทราบว่าฉันไม่ได้ "cd" ในไดเรกทอรีใด ๆ

ไชโย


3
มีตัวอย่างของ"การใช้งานที่ไร้ประโยชน์ls"ที่นี่สองสามครั้ง- คุณสามารถทำได้for dir in $YOUR_TOP_LEVEL_FOLDER/*แทน
Mark Longair

1
บางทีมันไร้ประโยชน์โดยทั่วไป แต่ก็อนุญาตให้ทำการกรองโดยตรงใน ls (เช่นไดเรกทอรีทั้งหมดที่ลงท้ายด้วย. tmp) นั่นเป็นเหตุผลที่ฉันใช้ls $dirนิพจน์
gforcada

13
for dir in PARENT/*
do
  test -d "$dir" || continue
  # Do something with $dir...
done

และนี่เป็นเรื่องง่ายที่จะเข้าใจ!
Rbjz

6

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

#!/bin/bash

#Use set -x if you want to echo each command while getting executed
#set -x

#Save current directory so we can restore it later
cur=$PWD
#Save command line arguments so functions can access it
args=("$@")

#Put your code in this function
#To access command line arguments use syntax ${args[1]} etc
function dir_command {
    #This example command implements doing git status for folder
    cd $1
    echo "$(tput setaf 2)$1$(tput sgr 0)"
    git tag -a ${args[0]} -m "${args[1]}"
    git push --tags
    cd ..
}

#This loop will go to each immediate child and execute dir_command
find . -maxdepth 1 -type d \( ! -name . \) | while read dir; do
   dir_command "$dir/"
done

#This example loop only loops through give set of folders    
declare -a dirs=("dir1" "dir2" "dir3")
for dir in "${dirs[@]}"; do
    dir_command "$dir/"
done

#Restore the folder
cd "$cur"

5

ฉันไม่ได้รับจุดที่มีการจัดรูปแบบของไฟล์เนื่องจากคุณต้องการวนซ้ำผ่านโฟลเดอร์ ... คุณกำลังมองหาสิ่งนี้หรือไม่?

cd parent
find . -type d | while read d; do
   ls $d/
done

4

คุณสามารถใช้ได้

find .

เพื่อค้นหาไฟล์ / dirs ทั้งหมดในไดเรกทอรีปัจจุบัน recurive

กว่าที่คุณจะสามารถส่งออกคำสั่ง xargs ได้

find . | xargs 'command here'

3
นี่ไม่ใช่สิ่งที่ถูกถาม
kenorb

0
for p in [0-9][0-9][0-9];do
    (
        cd $p
        for f in [0-9][0-9][0-9][0-9]*.txt;do
            ls $f; # Your operands
        done
    )
done

คุณจะต้องเปลี่ยนการสำรองข้อมูลไดเรกทอรีอีกครั้งหรือทำcdใน subshell
Mark Longair

Downvote: การแก้ไขสูญเสียcdรหัสนี้จึงไม่มีประโยชน์อะไร
tripleee
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.