Nazwy plików ze spacjami przedzielającymi pętlę, znajdź polecenie

34

Mam skrypt, który przeszukuje wszystkie pliki w wielu podfolderach i archiwach do tar. Mój skrypt jest

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

Polecenie find daje mi następujące dane wyjściowe

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

Ale zmienna FILE przechowuje tylko pierwszą część ścieżki ./F1/F1-2013-03-19, a następnie kolejną część 160413.csv .

Próbowałem użyć readpętli while,

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

ale pojawia się następujący błąd

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

Czy ktoś może zaproponować alternatywny sposób?

Aktualizacja

Zgodnie z sugestiami w poniższych odpowiedziach zaktualizowałem skrypty

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

Otrzymałem wynik

./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
Ubuntuser
źródło
4
Wygląda na to, że powinieneś ustawić IFS=$'\n'przed pętlą `for, aby parsować każdą linię
kiri

Odpowiedzi:

36

Używanie forz findjest tutaj niewłaściwym podejściem, patrz na przykład ten opis puszki otwieranych przez ciebie robaków.

Zalecanym sposobem jest użycie find, whilei readjak opisano tutaj . Poniżej znajduje się przykład, który powinien działać dla Ciebie:

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

W ten sposób rozgraniczasz nazwy plików \0znakami null ( ), co oznacza, że ​​zmiana przestrzeni i innych znaków specjalnych nie spowoduje problemów.

Aby zaktualizować archiwum z findlokalizowanymi plikami , możesz przekazać jego dane wyjściowe bezpośrednio do tar:

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

Pamiętaj, że nie musisz rozróżniać, czy archiwum istnieje, czy nie, tarobsłuży je rozsądnie. Zwróć też uwagę na użycie -printftutaj, aby uniknąć włączenia ./bitu do archiwum.

Thor
źródło
Dzięki, prawie działa. Jedyną rzeczą jest jego archiwizacja ./jako smoły. ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser,
@Ubuntuser Możesz dodać prosty czek, aby zobaczyćif [[ "$FILE" == "./" ]]; then continue
kiri
@Ubuntuser: Możesz tego uniknąć ./dzięki -printfzaktualizowanej odpowiedzi. Jednak nie powinno to mieć znaczenia, czy jest dołączone, czy nie, ponieważ odnosi się tylko do bieżącego katalogu. Podałem również alternatywną find/tarkombinację, której możesz chcieć użyć.
Thor
Dla tych, którzy chcą sortwyników przed iteracją, potrzebujesz sort -zseparatora zerowego.
Adambean
13

Spróbuj zacytować forpętlę w ten sposób:

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

Bez cudzysłowów bash nie radzi sobie dobrze ze spacjami i znakami nowej linii ( \n) ...

Spróbuj także ustawienia

IFS=$'\n'
kiri
źródło
1
+1 za $ IFS. To opisuje charakter separatora.
Ray
1
To rozwiązanie działało dla mnie. Użyłem commdo porównania posortowanych list plików, a nazwy plików zawierały spacje, pomimo cytowania zmiennych nie działało. Potem zobaczyłem, że cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html i rozwiązanie ustawienia $ IFS z IFS = $ (echo -en "\ n \ b") zadziałało dla mnie.
pbhj
Dodanie podwójnych cytatów, elegancki, prosty, piękny - dzięki!
Big Rich
8

To działa i jest prostsze:

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

Podziękowania dla Rupy ( https://github.com/rupa/z ) za tę odpowiedź.

ShawnMilo
źródło
4

Oprócz poprawnego cytowania możesz powiedzieć, findaby użyć separatora NULL, a następnie odczytać i przetworzyć wyniki w whilepętli

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

To powinno obsłużyć wszystkie nazwy plików, które są zgodne z POSIX - patrz 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
źródło
to jest jedyne rozwiązanie, które działało dla mnie. Dzięki.
codefreak
1
find . <find arguments> -print0 | xargs -0 grep <pattern>
użytkownik2802945
źródło
1

Zrobiłem coś takiego, aby znaleźć pliki, które mogą zawierać spacje.

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

Działa jak urok :)

Scott B.
źródło
0

Myślę, że lepiej będzie findskorzystać z opcji -exec.

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

Znajdź następnie wykonuje polecenie za pomocą wywołania systemowego, aby zachować spacje i znaki nowej linii (raczej potok, który wymagałby cytowania znaków specjalnych). Zauważ, że „tar -c” działa niezależnie od tego, czy archiwum już istnieje, i że (przynajmniej z bash) ani {}, ani + nie muszą być cytowane.

Drake Clarris
źródło
-1

Jak sugeruje minerz029, musisz zacytować rozszerzenie findpolecenia. Musisz także zacytować wszystkie podstawienia $FILEw swojej pętli.

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

Zauważ, że $()składnia powinna być preferowana przed użyciem backicks; patrz ten U L pytanie . Usunąłem również [[słowo kluczowe i zastąpiłem je [poleceniem, ponieważ jest to POSIX.

Joseph R.
źródło
Informacje o [[i [wydaje się, że [[jest nowszy i obsługuje więcej funkcji, takich jak globowanie i dopasowywanie wyrażeń regularnych. [[jest tylko w bashnie,sh
kiri
@ minerz029 Tak. Tak mówię. Nie wiem, co masz na myśli, [[mówiąc o wspieraniu globowania. Według wiki Grega wewnątrz globusa nie ma miejsca [[.
Joseph R.
Spróbuj [ "ab" == a? ] && echo "true"więc[[ "ab" == a? ]] && echo "true"
kiri,
@ minerz029 To nie jest globbing. Są to wyrażenia regularne (luźno interpretowane). To nie jest glob, ponieważ a*oznacza „po którym następuje dowolna liczba znaków”, a nie „wszystkie pliki, których nazwy zaczynają się ai mają później dowolną liczbę znaków”. Spróbuj [ ab = a* ] && echo true kontra [[ ab == a* ]] && echo true.
Joseph R.
Ach cóż, [[nadal robi wyrażenia regularne, podczas gdy [nie robi. Musiał się pomylić
kiri,