Nomi di file con spazi che si rompono per loop, trova il comando


34

Ho uno script che cerca tutti i file in più sottocartelle e archivi per tar. La mia sceneggiatura è

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

Il comando find mi dà il seguente output

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

Ma la variabile FILE memorizza solo la prima parte del percorso ./F1/F1-2013-03-19 e quindi la parte successiva 160413.csv .

Ho provato a usare readcon un ciclo while,

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

ma ottengo il seguente errore

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

Qualcuno può suggerire un modo alternativo?

Aggiornare

Come suggerito nelle risposte sotto ho aggiornato gli script

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

L'output che ottengo è

./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
Sembra che dovresti impostare IFS=$'\n'prima del ciclo `for per farlo analizzare da ogni riga
kiri,

Risposte:


36

Usando forcon findè l'approccio sbagliato qui, vedi ad esempio questo articolo sulla lattina di worm che stai aprendo.

L'approccio consigliato è quello di utilizzare find, whilee readcome descritto qui . Di seguito è riportato un esempio che dovrebbe funzionare per te:

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

In questo modo si delimitano i nomi di file con caratteri null ( \0), ciò significa che la variazione nello spazio e altri caratteri speciali non causeranno problemi.

Per aggiornare un archivio con i file che findtrova, è possibile passare l'output direttamente a tar:

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

Si noti che non è necessario distinguere se l'archivio esiste o no, tarlo gestirà in modo ragionevole. Si noti inoltre l'uso di -printfqui per evitare di includere il ./bit nell'archivio.


Grazie, funziona quasi. L'unica cosa è la sua archiviazione ./come catrame. ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser

@Ubuntuser Potresti aggiungere un semplice controllo per vedereif [[ "$FILE" == "./" ]]; then continue
kiri,

@Ubuntuser: puoi evitare il ./bit con la -printfrisposta aggiornata. Tuttavia, non dovrebbe fare alcuna differenza se è incluso o meno in quanto fa semplicemente riferimento alla directory corrente. Ho anche incluso una find/tarcombinazione alternativa che potresti voler usare.
Thor,

Per quelli di voi che desiderano sorti risultati prima di iterarli, è necessario sort -zil separatore null.
Adambean,

13

Prova a citare il forciclo in questo modo:

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

Senza virgolette, bash non gestisce bene spazi e newline ( \n) ...

Prova anche a impostare

IFS=$'\n'

1
+1 per $ IFS. Questo descrive il carattere separatore.
Ray,

1
Questa è la soluzione che ha funzionato per me. Stavo usando commper confrontare elenchi di file ordinati e i nomi dei file contenevano spazi, nonostante citando le variabili non funzionava. Poi ho visto cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html e la soluzione di impostare $ IFS con IFS = $ (echo -en "\ n \ b") ha funzionato per me.
pbhj,

Aggiunta delle doppie virgolette, elegante, semplice, bella - grazie!
Big Rich


4

Oltre alla corretta quotazione, puoi dire finddi usare un separatore NULL, quindi leggere ed elaborare i risultati in un whileciclo

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

Questo dovrebbe gestire tutti i nomi di file che sono conformi a POSIX - vedi 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.

questa è l'unica soluzione che ha funzionato per me. Grazie.
codefreak


1

Ho fatto qualcosa del genere per trovare file che potrebbero contenere spazi.

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

Ha funzionato come un fascino :)



0

Penso che potresti stare meglio usando findl'opzione -exec.

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

Trova quindi esegue il comando utilizzando una chiamata di sistema, in modo da preservare spazi e newline (piuttosto una pipe, che richiederebbe la citazione di caratteri speciali). Si noti che "tar -c" funziona indipendentemente dal fatto che l'archivio esista già e che (almeno con bash) né {} né + debbano essere citati.


-1

Come ha suggerito minerz029, è necessario citare l'espansione del findcomando. Devi anche citare tutte le sostituzioni di $FILEnel tuo ciclo.

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

Si noti che la $()sintassi dovrebbe essere preferita all'uso di backtick; vedi questa domanda U&L . Ho anche rimosso la [[parola chiave e sostituita dal [comando perché è POSIX.


A proposito [[e [, sembra che [[sia più recenti e supporta più funzioni come globbing e la corrispondenza regex. [[è solo in bash, però, nonsh
kiri,

@ minerz029 Sì. È quello che sto dicendo. Non capisco cosa intendi per [[supporto ai globbing. Secondo la wiki di Greg , all'interno non si verificano gorgogliamenti [[.
Joseph R.,

Prova [ "ab" == a? ] && echo "true"quindi[[ "ab" == a? ]] && echo "true"
kiri,

@ minerz029 Non è sconvolgente. Queste sono espressioni regolari (interpretate vagamente). Questo non è un glob perché a*significa "a seguito da un numero qualsiasi di caratteri" piuttosto che "tutti i file i cui nomi iniziano con ae che in seguito hanno un numero qualsiasi di caratteri". Prova [ ab = a* ] && echo true vs. [[ ab == a* ]] && echo true.
Joseph R.

Ah bene, [[fa ancora espressioni regolari mentre [non lo fa. Deve essersi confuso
kiri,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.