GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Dateien mit Leerzeichen im Namen durchlaufen??

Für diese Frage gibt es hier bereits Antworten :Warum ist das Überschleifen der Ausgabe von find eine schlechte Praxis?

(8 Antworten)
Vor 3 Jahren geschlossen.

Ich habe das folgende Skript geschrieben, um die Ausgaben von zwei Verzeichnissen mit denselben Dateien als solche zu unterscheiden:

#!/bin/bash

for file in `find . -name "*.csv"`  
do
     echo "file = $file";
     diff $file /some/other/path/$file;
     read char;
done

Ich weiß, dass es andere Möglichkeiten gibt, dies zu erreichen. Seltsamerweise schlägt dieses Skript jedoch fehl, wenn die Dateien Leerzeichen enthalten. Wie kann ich damit umgehen?

Beispielausgabe von find:

./zQuery - abc - Do Not Prompt for Date.csv

Akzeptierte Antwort:

Kurze Antwort (kommt Ihrer Antwort am nächsten, verarbeitet aber Leerzeichen)

OIFS="$IFS"
IFS=$'n'
for file in `find . -type f -name "*.csv"`  
do
     echo "file = $file"
     diff "$file" "/some/other/path/$file"
     read line
done
IFS="$OIFS"

Bessere Antwort (verarbeitet auch Platzhalter und Zeilenumbrüche in Dateinamen)

find . -type f -name "*.csv" -print0 | while IFS= read -r -d '' file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
done

Beste Antwort (basierend auf der Antwort von Gilles)

find . -type f -name '*.csv' -exec sh -c '
  file="$0"
  echo "$file"
  diff "$file" "/some/other/path/$file"
  read line </dev/tty
' exec-sh {} ';'

Oder noch besser, um zu vermeiden, dass ein sh ausgeführt wird pro Datei:

find . -type f -name '*.csv' -exec sh -c '
  for file do
    echo "$file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
  done
' exec-sh {} +

Lange Antwort

Sie haben drei Probleme:

  1. Standardmäßig teilt die Shell die Ausgabe eines Befehls auf Leerzeichen, Tabulatoren und Zeilenumbrüche auf
  2. Dateinamen könnten Platzhalterzeichen enthalten, die erweitert würden
  3. Was ist, wenn es ein Verzeichnis gibt, dessen Name auf *.csv endet? ?

1. Teilen nur bei Zeilenumbrüchen

Um herauszufinden, was file einzustellen ist Dazu muss die Shell die Ausgabe von find übernehmen und irgendwie interpretieren, sonst file wäre nur die gesamte Ausgabe von find .

Die Shell liest den IFS Variable, die auf <space><tab><newline> gesetzt ist standardmäßig.

Dann schaut es sich jedes Zeichen in der Ausgabe von find an . Sobald es irgendein Zeichen sieht, das in IFS ist , denkt es, dass dies das Ende des Dateinamens markiert, also setzt es file zu den Zeichen, die es bisher gesehen hat, und führt die Schleife aus. Dann beginnt es dort, wo es aufgehört hat, um den nächsten Dateinamen zu erhalten, und führt die nächste Schleife usw. aus, bis es das Ende der Ausgabe erreicht.

Es macht also effektiv Folgendes:

for file in "zquery" "-" "abc" ...

Um es anzuweisen, die Eingabe nur bei Zeilenumbrüchen aufzuteilen, müssen Sie Folgendes tun

IFS=$'n'

vor Ihrem for ... find Befehl.

Das setzt IFS zu einem einzelnen Zeilenumbruch, so dass es nur bei Zeilenumbrüchen geteilt wird und nicht auch bei Leerzeichen und Tabulatoren.

Wenn Sie sh verwenden oder dash statt ksh93 , bash oder zsh , müssen Sie IFS=$'n' schreiben stattdessen so:

IFS='
'

Das reicht wahrscheinlich aus, um Ihr Skript zum Laufen zu bringen, aber wenn Sie daran interessiert sind, einige andere Sonderfälle richtig zu handhaben, lesen Sie weiter …

2. Erweitern von $file ohne Platzhalter

Innerhalb der Schleife, wo Sie tun

diff $file /some/other/path/$file

die Shell versucht, $file zu expandieren (wieder!).

Es könnte Leerzeichen enthalten, aber da wir bereits IFS gesetzt haben oben, das ist hier kein Problem.

Er kann aber auch Platzhalterzeichen wie * enthalten oder ? , was zu unvorhersehbarem Verhalten führen würde. (Danke an Gilles für den Hinweis.)

Um der Shell mitzuteilen, dass Platzhalterzeichen nicht erweitert werden sollen, setzen Sie die Variable in doppelte Anführungszeichen, z. B.

diff "$file" "/some/other/path/$file"

Dasselbe Problem könnte auch uns beißen

for file in `find . -name "*.csv"`

Wenn Sie zum Beispiel diese drei Dateien hätten

file1.csv
file2.csv
*.csv

(sehr unwahrscheinlich, aber dennoch möglich)

Verwandte:Wenn ich Berechtigungen für eine TAR-Datei ändere, gilt das für die darin enthaltenen Dateien?

Es wäre, als ob Sie weggelaufen wären

for file in file1.csv file2.csv *.csv

die zu

erweitert wird
for file in file1.csv file2.csv *.csv file1.csv file2.csv

verursacht file1.csv und file2.csv zweimal verarbeitet werden.

Stattdessen müssen wir tun

find . -name "*.csv" -print | while IFS= read -r file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
done

read liest Zeilen von der Standardeingabe, zerlegt die Zeile gemäß IFS in Wörter und speichert sie in den von Ihnen angegebenen Variablennamen.

Hier teilen wir ihm mit, die Zeile nicht in Wörter aufzuteilen und die Zeile in $file zu speichern .

Beachten Sie auch, dass read line wurde in read line </dev/tty geändert .

Dies liegt daran, dass innerhalb der Schleife die Standardeingabe von find kommt über die Pipeline.

Wenn wir nur read hätten , es würde einen Teil oder den gesamten Dateinamen verbrauchen, und einige Dateien würden übersprungen.

/dev/tty ist das Terminal, von dem aus der Benutzer das Skript ausführt. Beachten Sie, dass dies einen Fehler verursacht, wenn das Skript über Cron ausgeführt wird, aber ich nehme an, dass dies in diesem Fall nicht wichtig ist.

Was ist dann, wenn ein Dateiname Zeilenumbrüche enthält?

Wir können das handhaben, indem wir -print ändern zu -print0 und mit read -d '' am Ende einer Pipeline:

find . -name "*.csv" -print0 | while IFS= read -r -d '' file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read char </dev/tty
done

Dies macht find Fügen Sie am Ende jedes Dateinamens ein Null-Byte ein. Nullbytes sind die einzigen Zeichen, die in Dateinamen nicht erlaubt sind, also sollte dies alle möglichen Dateinamen behandeln, egal wie seltsam.

Um den Dateinamen auf der anderen Seite zu erhalten, verwenden wir IFS= read -r -d '' .

Wo wir read verwendet haben oben haben wir das standardmäßige Zeilentrennzeichen von newline verwendet, aber jetzt find verwendet null als Zeilentrennzeichen. In bash , können Sie kein NUL-Zeichen in einem Argument an einen Befehl übergeben (auch nicht an eingebaute), aber bash versteht -d '' im Sinne von durch NUL getrennt . Also verwenden wir -d '' um read zu machen Verwenden Sie denselben Zeilenbegrenzer wie find . Beachten Sie, dass -d $'

Linux
  1. Finden Sie Dateien und Verzeichnisse unter Linux mit dem Befehl find

  2. Wie benenne ich den Satz von Dateien mit Muster um?

  3. Durchlaufen des Inhalts einer Datei in Bash

  4. Linux - Ersetzen von Leerzeichen in den Dateinamen

  5. Wie benenne ich Dateien mit Leerzeichen mit der Linux-Shell um?

So finden Sie Dateien mit dem fd-Befehl in Linux

Einfache Möglichkeit, Dateien mit dem Cat-Befehl zusammenzuführen

Bearbeiten von Dateien mit dem Plesk Control Panel-Dateimanager

So gehen Sie Dateinamen mit Leerzeichen in Linux an

So finden Sie Dateien mit Dutzenden von Kriterien mit dem Bash-Suchbefehl

Sichern Sie Linux mit der Sudoers-Datei