วิธีการวนซ้ำไฟล์ในไดเรกทอรีและเปลี่ยนเส้นทางและเพิ่มคำต่อท้ายชื่อไฟล์


562

ฉันต้องเขียนสคริปต์ที่เริ่มต้นโปรแกรมของฉันด้วยอาร์กิวเมนต์ที่ต่างกัน แต่ฉันใหม่สำหรับ Bash ฉันเริ่มโปรแกรมด้วย:

./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt].

นี่คือรหัสเทียมสำหรับสิ่งที่ฉันต้องการจะทำ:

for each filename in /Data do
  for int i = 0, i = 3, i++
    ./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
  end for
end for

ดังนั้นฉันจึงงงว่าจะสร้างอาร์กิวเมนต์ที่สองจากอันแรกได้อย่างไรดูเหมือน dataABCD_Log1.txt และเริ่มโปรแกรมของฉัน

คำตอบ:


736

คู่ของโน้ตแรก: เมื่อคุณใช้Data/data1.txtเป็นอาร์กิวเมนต์ควรเป็น/Data/data1.txt(ด้วยเครื่องหมายทับ) หรือไม่ นอกจากนี้ลูปภายนอกควรสแกนเฉพาะไฟล์. txt หรือไฟล์ทั้งหมดใน / Data ด้วยหรือไม่ นี่คือคำตอบสมมติว่า/Data/data1.txtและไฟล์. txt เท่านั้น:

#!/bin/bash
for filename in /Data/*.txt; do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

หมายเหตุ:

  • /Data/*.txtขยายไปยังเส้นทางของไฟล์ข้อความใน / Data ( รวมถึง / Data / ส่วน)
  • $( ... ) รันคำสั่งเชลล์และแทรกเอาต์พุตที่จุดนั้นในบรรทัดคำสั่ง
  • basename somepath .txtเอาต์พุตส่วนฐานของ somepath โดยลบ. txt ออกจากจุดสิ้นสุด (เช่น/Data/file.txt-> file)

หากคุณต้องการเรียกใช้ MyProgram ด้วยData/file.txtแทนที่จะ/Data/file.txtใช้"${filename#/}"เพื่อลบเครื่องหมายทับชั้นนำ ในทางตรงกันข้ามถ้ามันจริงๆDataไม่ได้คุณต้องการสแกนใช้เพียง/Datafor filename in Data/*.txt


1
เห็นได้ชัดว่าbasename -sเป็นส่วนขยายที่ไม่เป็นมาตรฐาน - ฉันจะแก้ไขคำตอบของฉันเพื่อใช้ไวยากรณ์มาตรฐาน
Gordon Davisson

21
หากไม่พบไฟล์ / จับคู่ไวด์การ์ดฉันกำลังค้นหาบล็อกลูปสำหรับรันลูปเรียกใช้ยังคงถูกป้อนอีกครั้งด้วย filename = "/Data/*.txt" ฉันจะหลีกเลี่ยงสิ่งนี้ได้อย่างไร
Oliver Pearmain

20
@OliverPearmain คุณสามารถใช้shopt -s nullglobก่อนลูป (และshopt -u nullglobหลังจากเพื่อหลีกเลี่ยงปัญหาในภายหลัง) หรือเพิ่มif [[ ! -e "$filename ]]; then continue; fiที่จุดเริ่มต้นของลูปดังนั้นมันจะข้ามไฟล์ที่ไม่มีอยู่
Gordon Davisson

9
สิ่งนี้จะไม่ทำงานเมื่อมีไฟล์ที่มีช่องว่างในชื่อ
Isa Hassen

4
@Isa ควรทำงานกับช่องว่างตราบใดที่เครื่องหมายคำพูดคู่อยู่ในตำแหน่ง ไม่ต้องใส่เครื่องหมายคำพูดใด ๆ เลยและคุณจะมีปัญหากับช่องว่าง
Gordon Davisson

344

ขออภัยสำหรับการทำเธรดให้เหมือนเดิม แต่เมื่อใดก็ตามที่คุณวนซ้ำไฟล์โดยการวนรอบมันเป็นแนวปฏิบัติที่ดีที่จะหลีกเลี่ยงกรณีมุมที่ glob ไม่ตรงกัน (ซึ่งทำให้ตัวแปรลูปขยายไปยังสตริงรูปแบบ glob (ไม่ตรงกัน)

ตัวอย่างเช่น:

for filename in Data/*.txt; do
    [ -e "$filename" ] || continue
    # ... rest of the loop body
done

การอ้างอิง: Bash Pitfalls


8
นี่ยังเป็นการเตือนที่ทันเวลา ฉันคิดว่าฉันได้สร้างสคริปต์ของฉันไม่ถูกต้อง แต่ฉันมีตัวพิมพ์เล็กกว่าส่วนขยายของไฟล์แทนที่จะเป็นตัวพิมพ์ใหญ่มันไม่พบไฟล์และคืนรูปแบบ glob ฮึ.
RufusVS

5
เนื่องจากใช้แท็ก Bash: นั่นคือสิ่งที่shopt nullglobมีไว้เพื่อ! (หรือshopt failglobสามารถใช้ได้เช่นกันขึ้นอยู่กับพฤติกรรมที่คุณต้องการ)
gniourf_gniourf

นอกจากนี้ยังตรวจสอบไฟล์ที่ถูกลบระหว่างการประมวลผลก่อนที่จะถึงลูป
loxaxs

1
จัดการกับกรณีที่คุณมีไดเรกทอรีชื่อdir.txt
vidstige

75
for file in Data/*.txt
do
    for ((i = 0; i < 3; i++))
    do
        name=${file##*/}
        base=${name%.txt}
        ./MyProgram.exe "$file" Logs/"${base}_Log$i.txt"
    done
done

name=${file##*/}เปลี่ยนตัว ( ขยายตัวพารามิเตอร์เปลือก ) เอาชื่อพา ธ /นำไปสู่การที่ผ่านมา

เปลี่ยนตัวเอาต่อท้ายbase=${name%.txt} .txtมันค่อนข้างยุ่งยากหากส่วนขยายสามารถเปลี่ยนแปลงได้


3
ฉันเชื่อว่ามีข้อผิดพลาดในรหัสของคุณ บรรทัดหนึ่งควรจะแทนbase=${name%.txt} base=${base%.txt}
caseklim

4
@CaseyKlimkowsky: ใช่; เมื่อรหัสและความคิดเห็นไม่เห็นด้วยอย่างน้อยหนึ่งข้อผิดพลาด ในกรณีนี้ฉันคิดว่ามันเป็นเพียงรหัสเดียวเท่านั้น บ่อยครั้งเป็นจริงทั้งที่ผิด ขอบคุณสำหรับการชี้ให้เห็น; ฉันแก้ไขมันแล้ว
Jonathan Leffler

8

คุณสามารถใช้ค้นหาตัวเลือกเอาต์พุตที่คั่นด้วย null พร้อมกับอ่านเพื่อวนซ้ำโครงสร้างไดเร็กทอรีได้อย่างปลอดภัย

#!/bin/bash
find . -type f -print0 | while IFS= read -r -d $'\0' file; 
  do echo "$file" ;
done

ดังนั้นสำหรับกรณีของคุณ

#!/bin/bash
find . -maxdepth 1 -type f  -print0 | while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done

นอกจากนี้

#!/bin/bash
while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done < <(find . -maxdepth 1 -type f  -print0)

จะเรียกใช้ลูป while ในขอบเขตปัจจุบันของสคริปต์ (กระบวนการ) และอนุญาตให้ใช้เอาต์พุตการค้นหาเพื่อตั้งค่าตัวแปรหากจำเป็น


2
$'\0'''เป็นวิธีที่แปลกของการเขียน คุณกำลังขาดหายไปIFS=และ-rสลับไปread: IFS= read -rd '' fileคำสั่งอ่านของคุณควรจะเป็น:
gniourf_gniourf

ฉันคิดว่าบางคนต้องการการค้นหา$'\0'และกระจายคะแนนสแต็กไปรอบ ๆ จะทำการแก้ไขที่คุณชี้ให้เห็น สิ่งที่เป็นผลร้ายของการไม่IFS=พยายามecho -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; doneไม่มีดูเหมือนใด ๆ นอกจากนี้ -r ฉันเห็นมักจะเป็นค่าเริ่มต้น แต่ไม่สามารถหาตัวอย่างสำหรับสิ่งที่ป้องกันไม่ให้เกิดขึ้น
James

4
IFS=เป็นสิ่งจำเป็นในกรณีที่ชื่อไฟล์ลงท้ายด้วยช่องว่าง: ลองใช้กับtouch 'Prospero '(สังเกตพื้นที่ต่อท้าย) นอกจากนี้คุณจำเป็นต้องมี-rสวิทช์ในกรณีที่ชื่อไฟล์ที่มีเครื่องหมาย: touch 'Prospero\n'ลองกับ
gniourf_gniourf

-1

ดูเหมือนว่าคุณกำลังพยายามเรียกใช้ไฟล์ windows (.exe) แน่นอนว่าคุณควรใช้ PowerShell อย่างไรก็ตามใน bash shell ของ Linux เพียงแค่ซับเดียวก็พอเพียง

[/home/$] for filename in /Data/*.txt; do for i in {0..3}; do ./MyProgam.exe  Data/filenameLogs/$filename_log$i.txt; done done

หรือในการทุบตี

#!/bin/bash

for filename in /Data/*.txt; 
   do
     for i in {0..3}; 
       do ./MyProgam.exe Data/filename.txt Logs/$filename_log$i.txt; 
     done 
 done
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.