Nama file dengan spasi untuk perulangan, temukan perintah

34

Saya memiliki skrip yang mencari semua file dalam beberapa subfolder dan arsip untuk tar. Skrip saya adalah

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

Perintah find memberi saya output berikut

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

Tetapi variabel FILE hanya menyimpan bagian pertama dari path ./F1/F1-2013-03-19 dan kemudian bagian selanjutnya 160413.csv .

Saya mencoba menggunakan readdengan loop sementara,

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

tapi saya mendapatkan kesalahan berikut

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

Adakah yang bisa menyarankan cara alternatif?

Memperbarui

Seperti yang disarankan dalam jawaban di bawah ini saya memperbarui skrip

#!/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

Output yang saya dapatkan adalah

./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
Pengguna aktif
sumber
4
Tampaknya Anda harus mengatur IFS=$'\n'sebelum `for loop untuk membuatnya parse di setiap baris
kiri

Jawaban:

36

Menggunakan fordengan findadalah pendekatan yang salah di sini, lihat misalnya artikel ini tentang kaleng cacing yang Anda buka.

Pendekatan yang disarankan adalah menggunakan find, whiledan readseperti yang dijelaskan di sini . Di bawah ini adalah contoh yang cocok untuk Anda:

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

Dengan cara ini Anda membatasi nama file dengan \0karakter null ( ), ini berarti bahwa variasi dalam ruang dan karakter khusus lainnya tidak akan menimbulkan masalah.

Untuk memperbarui arsip dengan file yang ditemukan find, Anda dapat meneruskan hasilnya langsung ke tar:

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

Perhatikan bahwa Anda tidak harus membedakan antara apakah arsip itu ada atau tidak, tarakan menanganinya dengan bijaksana. Perhatikan juga penggunaan di -printfsini untuk menghindari memasukkan ./bit dalam arsip.

Thor
sumber
Terima kasih, hampir berhasil. Satu-satunya hal adalah pengarsipannya ./sebagai tar. ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser
@Ubuntuser Anda bisa menambahkan cek sederhana untuk melihatif [[ "$FILE" == "./" ]]; then continue
kiri
@Ubuntuser: Anda dapat menghindari ./bit dengan -printfmelihat jawaban yang diperbarui. Namun itu tidak akan membuat perbedaan jika dimasukkan atau tidak karena hanya referensi direktori saat ini. Saya juga menyertakan find/tarkombinasi alternatif yang mungkin ingin Anda gunakan.
Thor
Bagi Anda yang menginginkan sorthasil sebelum mengulanginya, Anda akan membutuhkan sort -zpemisah nol.
Adambean
13

Coba kutip forlingkaran seperti ini:

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

Tanpa tanda kutip, bash tidak menangani spasi dan baris baru ( \n) dengan baik ...

Coba juga pengaturan

IFS=$'\n'
kiri
sumber
1
+1 untuk $ IFS. Itu mendeskripsikan karakter pemisah.
Ray
1
Ini adalah solusi yang berhasil untuk saya. Saya menggunakan communtuk membandingkan daftar file yang diurutkan dan nama file memiliki spasi di dalamnya, meskipun mengutip variabel itu tidak berfungsi. Kemudian saya melihat cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html dan solusi pengaturan $ IFS dengan IFS = $ (echo -en "\ n \ b") bekerja untuk saya.
pbhj
Penambahan kutipan ganda, elegan, sederhana, indah - terima kasih!
Kaya Besar
8

Ini berfungsi dan lebih sederhana:

find . -name '<pattern>' | while read LINE; do echo "$LINE" ; done

Penghargaan untuk Rupa ( https://github.com/rupa/z ) untuk jawaban ini.

ShawnMilo
sumber
4

Selain mengutip yang tepat, Anda dapat meminta finduntuk menggunakan pemisah NULL, dan kemudian membaca dan memproses hasilnya dalam satu whilelingkaran

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

Ini harus menangani nama file apa pun yang sesuai dengan POSIX - lihat 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.
Steeldriver
sumber
ini hanya solusi yang bekerja untuk saya. Terima kasih.
codefreak
1
find . <find arguments> -print0 | xargs -0 grep <pattern>
pengguna2802945
sumber
1

Saya melakukan sesuatu seperti ini untuk menemukan file yang mungkin berisi spasi.

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

Bekerja seperti pesona :)

Scott B
sumber
0

Saya pikir Anda mungkin lebih baik menggunakan findopsi -exec.

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

Temukan kemudian jalankan perintah menggunakan system call, sehingga spasi dan baris baru dipertahankan (lebih seperti pipa, yang akan memerlukan mengutip karakter khusus). Perhatikan bahwa "tar -c" berfungsi apakah arsip sudah ada atau belum, dan (setidaknya dengan bash) baik {} maupun + perlu dikutip.

Drake Clarris
sumber
-1

Seperti yang disarankan minerz029, Anda perlu mengutip perluasan findperintah. Anda juga perlu mengutip semua pergantian dari $FILEdalam loop Anda.

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

Perhatikan bahwa $()sintaksis lebih disukai daripada penggunaan backticks; lihat pertanyaan U&L ini . Saya juga menghapus [[kata kunci dan menggantinya dengan [perintah karena itu POSIX.

Joseph R.
sumber
Tentang [[dan [, tampaknya itu [[lebih baru dan mendukung lebih banyak fitur seperti globbing dan pencocokan regex. [[hanya di bash, bukansh
kiri
@ minerz029 Ya. Itu yang saya katakan. Saya tidak tahu apa yang Anda maksud dengan [[mendukung globbing. Menurut wiki Greg , tidak ada globbing yang terjadi di dalam [[.
Joseph R.
Coba [ "ab" == a? ] && echo "true"kemudian[[ "ab" == a? ]] && echo "true"
kiri
@ minerz029 Itu tidak menggumpal. Ini adalah ekspresi reguler (ditafsirkan secara longgar). Ini bukan bola karena a*berarti "diikuti oleh sejumlah karakter" daripada "semua file yang namanya dimulai dengan adan memiliki sejumlah karakter sesudahnya". Coba [ ab = a* ] && echo true vs [[ ab == a* ]] && echo true..
Joseph R.
Ah well, [[masih melakukan ekspresi reguler sementara [tidak. Pasti bingung
kiri