Dateinamen mit Leerzeichen für Schleife, Befehl find

34

Ich habe ein Skript, das alle Dateien in mehreren Unterordnern und Archiven nach tar durchsucht. Mein Drehbuch ist

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

Der Befehl find gibt die folgende Ausgabe aus

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

Die Variable FILE speichert jedoch nur den ersten Teil des Pfads ./F1/F1-2013-03-19 und dann den nächsten Teil 160413.csv .

Ich habe versucht readmit einer while-Schleife,

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

aber ich bekomme folgenden fehler

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

Kann mir jemand einen alternativen Weg vorschlagen?

Aktualisieren

Wie in den Antworten unten vorgeschlagen, habe ich die Skripte aktualisiert

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

Die Ausgabe, die ich erhalte, ist

./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
quelle
4
Es sieht so aus, IFS=$'\n'als ob Sie vor die `for-Schleife setzen sollten, damit sie von jeder Zeile analysiert wird
kiri

Antworten:

36

Die Verwendung von forwith findist hier der falsche Ansatz. Sehen Sie sich zum Beispiel diesen Artikel über die Dose Würmer an, die Sie öffnen.

Der empfohlene Ansatz ist die Verwendung find, whileund readwie hier . Nachfolgend finden Sie ein Beispiel, das für Sie funktionieren sollte:

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

Auf diese Weise begrenzen Sie die Dateinamen mit Null ( \0) - Zeichen. Dies bedeutet, dass Variationen im Leerzeichen und in anderen Sonderzeichen keine Probleme verursachen.

Um ein Archiv mit den gefundenen Dateien zu aktualisieren find, können Sie die Ausgabe direkt an tarfolgende Adresse übergeben :

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

Beachten Sie, dass Sie nicht unterscheiden müssen, ob das Archiv existiert oder nicht, tarwird es sinnvoll behandeln. Beachten Sie auch die Verwendung von -printfhier, um zu vermeiden, dass das ./Bit in das Archiv aufgenommen wird.

Thor
quelle
Danke, es funktioniert fast. Das Einzige ist die Archivierung des ./as tar. ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser
@ Ubuntuser Sie könnten einen einfachen Scheck hinzufügen, um zu sehenif [[ "$FILE" == "./" ]]; then continue
kiri
@Ubuntuser: Sie können das ./Bit mit der -printfaktualisierten Antwort umgehen . Es sollte jedoch keinen Unterschied machen, ob es enthalten ist oder nicht, da es nur auf das aktuelle Verzeichnis verweist. Ich habe auch eine alternative find/tarKombination beigefügt , die Sie möglicherweise verwenden möchten.
Thor
Für diejenigen von Ihnen, die sortdie Ergebnisse vor der Iteration sehen möchten, benötigen Sie sort -zdas Null-Trennzeichen.
Adambean
13

Versuchen Sie das zu zitieren for Schleife wie :

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

Ohne Anführungszeichen kann bash mit Leerzeichen und Zeilenumbrüchen ( \n) überhaupt nicht umgehen ...

Versuchen Sie auch die Einstellung

IFS=$'\n'
kiri
quelle
1
+1 für $ IFS. Das beschreibt das Trennzeichen.
Ray
1
Dies ist die Lösung, die für mich funktioniert hat. Ich habe commsortierte Dateilisten verglichen und die Dateinamen enthielten Leerzeichen, obwohl es nicht funktionierte, Variablen zu zitieren. Dann sah ich cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html und die Lösung, $ IFS mit IFS = $ (echo -en "\ n \ b") zu setzen, funktionierte für mich.
pbhj
Hinzufügung der doppelten Anführungszeichen, elegant, einfach, schön - danke!
Big Rich
8

Das funktioniert und ist einfacher:

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

Wir danken Rupa ( https://github.com/rupa/z ) für diese Antwort.

ShawnMilo
quelle
4

Zusätzlich zur korrekten Anführungszeichen können Sie die findVerwendung eines NULL-Trennzeichens angeben und dann die Ergebnisse in einer whileSchleife lesen und verarbeiten

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

Dies sollte alle Dateinamen behandeln, die POSIX-kompatibel sind - siehe 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.
Stahlfahrer
quelle
Dies ist die einzige Lösung, die bei mir funktioniert hat. Vielen Dank.
Codefreak
1
find . <find arguments> -print0 | xargs -0 grep <pattern>
user2802945
quelle
1

Ich habe so etwas gemacht, um Dateien zu finden, die Leerzeichen enthalten können.

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

Lief wie am Schnürchen :)

Scott B
quelle
0

Ich denke, Sie könnten besser dran sein find, wenn Sie die Option -exec verwenden.

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

Find führt dann den Befehl über einen Systemaufruf aus, sodass Leerzeichen und Zeilenumbrüche erhalten bleiben (eher eine Pipe, für die Sonderzeichen in Anführungszeichen gesetzt werden müssten). Beachten Sie, dass "tar -c" funktioniert, unabhängig davon, ob das Archiv bereits existiert oder nicht, und dass (zumindest mit bash) weder {} noch + in Anführungszeichen gesetzt werden müssen.

Drake Clarris
quelle
-1

Wie von minerz029 vorgeschlagen, müssen Sie die Erweiterung des findBefehls angeben . Sie müssen auch alle Substitutionen von $FILEin Ihrer Schleife angeben.

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

Beachten Sie, dass die $()Syntax der Verwendung von Backticks vorzuziehen ist. siehe diese U & L-Frage . Ich entfernte auch das [[Schlüsselwort und ersetzte es durch den [Befehl, weil es POSIX ist.

Joseph R.
quelle
Über [[und [scheint es, dass [[es neuer ist und mehr Funktionen wie Globbing und Regex Matching unterstützt. [[ist nur in bash, aber nichtsh
kiri
@ minerz029 Ja. Sag ich doch. Ich weiß nicht, was Sie unter [[Globbing verstehen . Laut Gregs Wiki findet im Inneren kein Globbing statt [[.
Joseph R.
Versuchen Sie es [ "ab" == a? ] && echo "true"dann[[ "ab" == a? ]] && echo "true"
kiri
@ minerz029 Das ist kein Problem. Dies sind reguläre Ausdrücke (lose interpretiert). Dies ist kein Glob, da a*dies "gefolgt von einer beliebigen Anzahl von Zeichen" bedeutet und nicht "alle Dateien, deren Namen mit aeiner beliebigen Anzahl von Zeichen beginnen und danach". Versuchen [ ab = a* ] && echo true vs. [[ ab == a* ]] && echo true.
Joseph R.
Ah gut, [[tut noch reguläre Ausdrücke, während [nicht. Muss verwirrt sein
kiri