ชื่อไฟล์ที่มีช่องว่างสำหรับลูปค้นหาคำสั่ง


33

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

for FILE in `find . -type f  -name '*.*'`
  do
if [[ ! -f archive.tar ]]; then

  tar -cpf archive.tar $FILE
else 
  tar -upf archive.tar $FILE 
fi
done

คำสั่ง find ให้ผลลัพธ์ต่อไปนี้แก่ฉัน

find . -type f  -iname '*.*'
./F1/F1-2013-03-19 160413.csv
./F1/F1-2013-03-19 164411.csv
./F1-FAILED/F2/F1-2013-03-19 154412.csv
./F1-FAILED/F3/F1-2011-10-02 212910.csv
./F1-ARCHIVE/F1-2012-06-30 004408.csv
./F1-ARCHIVE/F1-2012-05-08 190408.csv

แต่FILEตัวแปรเพียงเก็บส่วนแรกของเส้นทาง./F1/F1-2013-03-19แล้วส่วนถัดไป160413.csv

ฉันลองใช้readกับลูปสักครู่

while read `find . -type f  -iname '*.*'`;   do ls $REPLY; done

แต่ฉันได้รับข้อผิดพลาดดังต่อไปนี้

bash: read: `./F1/F1-2013-03-19': not a valid identifier

ทุกคนสามารถแนะนำวิธีอื่นได้หรือไม่?

ปรับปรุง

ตามที่แนะนำในคำตอบด้านล่างฉันอัปเดตสคริปต์

#!/bin/bash

INPUT_DIR=/usr/local/F1
cd $INPUT_DIR
for FILE in "$(find  . -type f -iname '*.*')"
do
archive=archive.tar

        if [ -f $archive ]; then
        tar uvf $archive "$FILE"
        else
        tar -cvf $archive "$FILE"
        fi
done

ผลลัพธ์ที่ฉันได้รับคือ

./test.sh
tar: ./F1/F1-2013-03-19 160413.csv\n./F1/F1-2013-03-19 164411.csv\n./F1/F1-2013-03-19 153413.csv\n./F1/F1-2013-03-19 154412.csv\n./F1/F1-2012-09-10 113409.csv\n./F1/F1-2013-03-19 152411.csv\n./.tar\n./F1-FAILED/F3/F1-2013-03-19 154412.csv\n./F1-FAILED/F3/F1-2013-03-19 170411.csv\n./F1-FAILED/F3/F1-2012-09-10 113409.csv\n./F1-FAILED/F2/F1-2011-10-03 113911.csv\n./F1-FAILED/F2/F1-2011-10-02 165908.csv\n./F1-FAILED/F2/F1-2011-10-02 212910.csv\n./F1-ARCHIVE/F1-2012-06-30 004408.csv\n./F1-ARCHIVE/F1-2011-08-17 133905.csv\n./F1-ARCHIVE/F1-2012-10-21 154410.csv\n./F1-ARCHIVE/F1-2012-05-08 190408.csv: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors

4
ดูเหมือนว่าคุณควรตั้งค่าไว้IFS=$'\n'ก่อนหน้า `for loop เพื่อให้แต่ละบรรทัดแยกวิเคราะห์
kiri

1
นี่คือการอ่านที่เกี่ยวข้องบางอย่าง: mywiki.wooledge.org/ParsingLs AND dwheeler.com/essays/filenames-in-shell.htmlและunix.stackexchange.com/questions/128985/why-not-parse-ls
Sergiy Kolodyazhnyy

คำตอบ:


36

การใช้forกับfindเป็นวิธีการที่ผิดที่นี่ดูตัวอย่างการเขียนเกี่ยวกับความสามารถของเวิร์มที่คุณกำลังเปิดอยู่

วิธีที่แนะนำคือการใช้งานfind, whileและreadตามที่อธิบายไว้ที่นี่ ด้านล่างเป็นตัวอย่างที่เหมาะกับคุณ:

find . -type f -name '*.*' -print0 | 
while IFS= read -r -d '' file; do
    printf '%s\n' "$file"
done

วิธีนี้คุณคั่นชื่อไฟล์ด้วย\0อักขระnull ( ) ซึ่งหมายความว่าการเปลี่ยนแปลงในช่องว่างและอักขระพิเศษอื่น ๆ จะไม่ทำให้เกิดปัญหา

ในการอัปเดตไฟล์เก็บถาวรด้วยไฟล์ที่findตั้งคุณสามารถส่งออกโดยตรงไปที่tar:

find . -type f -name '*.*' -printf '%p\0' | 
tar --null -uf archive.tar -T -

โปรดทราบว่าคุณไม่จำเป็นต้องแยกแยะระหว่างที่เก็บถาวรนั้นมีอยู่หรือไม่tarจะจัดการกับมันอย่างสมเหตุสมผล นอกจากนี้โปรดทราบว่าการใช้-printfที่นี่เพื่อหลีกเลี่ยงการรวม./บิตในที่เก็บถาวร


ขอบคุณมันใช้งานได้เกือบ สิ่งเดียวคือการเก็บถาวร./เป็นน้ำมันดิน ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser

@Ubuntuser คุณสามารถเพิ่มการตรวจสอบง่าย ๆ เพื่อดูif [[ "$FILE" == "./" ]]; then continue
kiri

@Ubuntuser: คุณสามารถหลีกเลี่ยง./บิตด้วยการ-printfดูคำตอบที่อัปเดต อย่างไรก็ตามมันไม่ควรสร้างความแตกต่างใด ๆ ถ้ามันรวมอยู่หรือไม่เป็นเพียงแค่อ้างอิงไดเรกทอรีปัจจุบัน ฉันยังรวมfind/tarชุดค่าผสมอื่นที่คุณอาจต้องการใช้
ธ.ค.

สำหรับคนที่คุณต้องการsortผลลัพธ์ก่อนที่จะทำการวนซ้ำคุณจะต้องsort -zมีตัวคั่น null
Adambean

13

ลองอ้างอิงforลูปดังนี้:

for FILE in "`find . -type f  -name '*.*'`"   # note the quotation marks

ถ้าไม่มีเครื่องหมายอัญประกาศ bash จะไม่จัดการช่องว่างและขึ้นบรรทัดใหม่ ( \n) เลย ...

ลองตั้งค่า

IFS=$'\n'

1
+1 สำหรับ $ IFS นั่นสืบทอดอักขระตัวคั่น
เรย์

1
นี่คือทางออกที่ได้ผลสำหรับฉัน ฉันใช้commเพื่อเปรียบเทียบรายชื่อไฟล์เรียงและชื่อไฟล์มีช่องว่างในพวกเขาแม้จะมีการอ้างอิงตัวแปรมันไม่ทำงาน จากนั้นฉันก็เห็นcyberciti.biz/tips/handling-filenames-with-spaces-in-bash.htmlและวิธีแก้ปัญหาของการตั้งค่า $ IFS ด้วย IFS = $ (echo -en "\ n \ b")
pbhj

นอกจากคำพูดสองคำที่สง่างามเรียบง่ายสวยงาม - ขอบคุณ!
Big Rich


4

นอกเหนือจากการอ้างอิงที่เหมาะสมคุณสามารถบอกfindให้ใช้ตัวคั่น NULL แล้วอ่านและประมวลผลผลลัพธ์ในwhileลูป

while read -rd $'\0' file; do
    something with "$file"
done < <(find  . -type f -name '*.*' -print0)

สิ่งนี้ควรจัดการกับชื่อไฟล์ที่สอดคล้องกับ POSIX - ดู man find

   -print0
          True; print the full file name on the standard output, followed by a null character (instead of the newline character that  -print  uses).   This  allows  file
          names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output.  This option corresponds to the
          -0 option of xargs.

นี่เป็นทางออกเดียวที่ใช้ได้สำหรับฉัน ขอบคุณ
codefreak


1

ฉันทำสิ่งนี้เพื่อค้นหาไฟล์ที่อาจมีช่องว่าง

IFS=$'\n'
for FILE in `/usr/bin/find $DST/shared -name *.nsf | grep -v bookmark.nsf | grep -v names.nsf`; do
    file $FILE | tee -a $LOG
done

ทำงานเหมือนมีเสน่ห์ :)


0

คำตอบส่วนใหญ่จะแตกถ้ามีอักขระขึ้นบรรทัดใหม่ในชื่อไฟล์ ฉันใช้ทุบตีมากกว่า 15 ปี แต่มีการโต้ตอบเท่านั้น

ใน Python คุณสามารถใช้ os.walk (): http://docs.python.org/2/library/os.html#os.walk

และโมดูล tarfile: http://docs.python.org/2/library/tarfile.html#tar-examples


0

ฉันคิดว่าคุณอาจจะดีกว่าโดยใช้findตัวเลือก -exec

find . -type f -name '*.*' -exec tar -cpf archive.tar {} +

ค้นหาจากนั้นดำเนินการคำสั่งโดยใช้การเรียกของระบบเพื่อให้เว้นวรรคและบรรทัดใหม่ถูกเก็บรักษาไว้ (แทนที่จะเป็นไพพ์ซึ่งจะต้องมีการอ้างถึงอักขระพิเศษ) โปรดทราบว่า "tar -c" ทำงานได้หรือไม่ว่ามีไฟล์เก็บถาวรอยู่หรือไม่และอย่างน้อยด้วย bash นั้นไม่จำเป็นต้องอ้างถึง {} หรือ +


-1

ตามที่แนะนำ minerz029 คุณจำเป็นต้องอ้างอิงการขยายfindคำสั่ง คุณต้องอ้างการแทนที่ทั้งหมด$FILEในลูปของคุณด้วย

for FILE in "$(find . -type f  -name '*.*')"
do
    if [ ! -f archive.tar ]; then
        tar -cpf archive.tar "$FILE"
    else 
        tar -upf archive.tar "$FILE" 
    fi
done

โปรดทราบว่า$()ควรใช้ไวยากรณ์เพื่อการ backticks; ดูคำถาม U & Lนี้ ฉันยังลบ[[คำหลักและแทนที่ด้วย[คำสั่งเพราะเป็น POSIX


เกี่ยวกับ[[และ[ดูเหมือนว่า[[จะใหม่กว่าและรองรับคุณสมบัติเพิ่มเติมเช่นการจับคู่แบบกลมและการจับคู่ regex [[มี แต่ในเท่านั้นbashไม่ใช่sh
kiri

@ minerz029 ใช่ นั่นคือสิ่งที่ฉันพูด ฉันไม่รู้ว่าคุณหมายถึงอะไรโดยการ[[สนับสนุนกลมกลืน ตามที่วิกิพีเดียของเกร็กไม่มี globbing [[จะใช้เวลาภายในสถานที่
โจเซฟอาร์

ลอง[ "ab" == a? ] && echo "true"แล้ว[[ "ab" == a? ]] && echo "true"
kiri

@ minerz029 นั่นไม่โค้งงอ นี่เป็นนิพจน์ทั่วไป (ตีความอย่างหลวม) นี่ไม่ใช่รูปวงกลมเพราะa*หมายถึง "ตามด้วยจำนวนอักขระ" แทนที่จะเป็น "ไฟล์ทั้งหมดที่มีชื่อขึ้นต้นด้วยaและมีจำนวนอักขระหลังจากนั้น" ลองเทียบกับ[ ab = a* ] && echo true [[ ab == a* ]] && echo true
โจเซฟอาร์

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