Die Verwendung find . -print0
scheint der einzig sichere Weg zu sein, um eine Liste der Dateien in Bash zu erhalten, da Dateinamen Leerzeichen, Zeilenumbrüche, Anführungszeichen usw. enthalten können.
Es fällt mir jedoch schwer, die Ausgabe von find innerhalb von Bash oder mit anderen Befehlszeilenprogrammen nützlich zu machen. Die einzige Möglichkeit, die Ausgabe zu nutzen, besteht darin, sie an Perl weiterzuleiten und das IFS von Perl in Null zu ändern:
find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'
In diesem Beispiel wird die Anzahl der gefundenen Dateien gedruckt, um die Gefahr zu vermeiden, dass Zeilenumbrüche in Dateinamen die Anzahl beschädigen, wie dies bei folgenden Fällen der Fall wäre:
find . | wc -l
Da die meisten Befehlszeilenprogramme keine durch Nullen getrennten Eingaben unterstützen, ist es meines Erachtens am besten, die Ausgabe find . -print0
in einem Bash-Array zu erfassen , wie ich es im obigen Perl-Snippet getan habe, und dann mit der Aufgabe fortzufahren, wie auch immer Sein.
Wie kann ich das machen?
Das funktioniert nicht:
find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )
Eine viel allgemeinere Frage könnte sein: Wie kann ich nützliche Dinge mit Listen von Dateien in Bash tun?
Antworten:
Schamlos aus Gregs BashFAQ gestohlen :
unset a i while IFS= read -r -d $'\0' file; do a[i++]="$file" # or however you want to process each file done < <(find /tmp -type f -print0)
Beachten Sie, dass das hier verwendete Umleitungskonstrukt (
cmd1 < <(cmd2)
) dem der üblichen Pipeline (cmd2 | cmd1
) ähnelt, jedoch nicht ganz dem entspricht. Wenn es sich bei den Befehlen um Shell-Builds handelt (z. B.while
), werden sie von der Pipeline-Version in Subshells und allen von ihnen festgelegten Variablen ausgeführt (zB das Arraya
) gehen beim Beenden verloren.cmd1 < <(cmd2)
Läuft nur cmd2 in einer Subshell, sodass das Array über seine Konstruktion hinaus lebt. Warnung: Diese Form der Umleitung ist nur in Bash verfügbar, nicht einmal in Bash im Sh-Emulationsmodus. Sie müssen Ihr Skript mit beginnen#!/bin/bash
.Da die
a[i++]="$file"
Eingabe des Dateiverarbeitungsschritts (in diesem Fall nur , aber Sie möchten möglicherweise etwas ausgefalleneres direkt in der Schleife ausführen) umgeleitet wird, kann er keine Befehle verwenden, die möglicherweise von stdin lesen. Um diese Einschränkung zu vermeiden, verwende ich normalerweise:unset a i while IFS= read -r -u3 -d $'\0' file; do a[i++]="$file" # or however you want to process each file done 3< <(find /tmp -type f -print0)
... die die Dateiliste über Einheit 3 und nicht über stdin übergibt.
quelle
\177
) in Zeichenfolgen befassten . IIRC, sogar x = "$ y" würde mit diesen Zeichen nicht immer richtig funktionieren. Ich habe gerade mit Bash 2.05b.0 und 3.2.17 getestet (das älteste und neueste, das ich zur Hand habe); Beide behandelten Zeilenumbrüche ordnungsgemäß, aber v2.05b.0 aß das Löschzeichen.-d ''
ist äquivalent zu-d $'\0'
.arr+=("$file")
Vielleicht suchen Sie nach xargs:
Die Option -L 1 könnte auch für Sie nützlich sein, wodurch xargs exec do_something_useful mit nur 1 Dateiargument macht.
quelle
Das Hauptproblem ist, dass das Trennzeichen NUL (\ 0) hier unbrauchbar ist, da es nicht möglich ist, IFS einen NUL-Wert zuzuweisen. Als gute Programmierer achten wir also darauf, dass die Eingabe für unser Programm etwas ist, das es verarbeiten kann.
Zuerst erstellen wir ein kleines Programm, das diesen Teil für uns erledigt:
#!/bin/bash printf "%s" "$@" | base64
... und nenne es base64str (chmod + x nicht vergessen)
Zweitens können wir jetzt eine einfache und unkomplizierte for-Schleife verwenden:
for i in `find -type f -exec base64str '{}' \;` do file="`echo -n "$i" | base64 -d`" # do something with file done
Der Trick ist also, dass ein base64-String kein Vorzeichen hat, was Probleme für bash verursacht - natürlich kann auch ein xxd oder ähnliches die Arbeit erledigen.
quelle
read -r -d ''
werde alles bis zum nächsten NUL in lesen"$REPLY"
. Es besteht kein Grund zur SorgeIFS
.Seit Bash 4.4 hat das eingebaute
mapfile
den-d
Schalter (um ein Trennzeichen anzugeben, ähnlich dem-d
Schalter derread
Anweisung), und das Trennzeichen kann das Nullbyte sein. Daher eine schöne Antwort auf die Frage im Titelist:
mapfile -d '' ary < <(find . -print0)
quelle
Noch eine andere Art, Dateien zu zählen:
find /DIR -type f -print0 | tr -dc '\0' | wc -c
quelle
Damit können Sie sicher zählen:
find . -exec echo ';' | wc -l
(Es druckt eine neue Zeile für jede gefundene Datei / jedes gefundene Verzeichnis und zählt dann die ausgedruckten Zeilenumbrüche ...)
quelle
-printf
Option anstelle-exec
für jede Datei zu verwenden:find . -printf "\n" | wc -l
Ich denke, es gibt elegantere Lösungen, aber ich werde diese einwerfen. Dies funktioniert auch für Dateinamen mit Leerzeichen und / oder Zeilenumbrüchen:
i=0; for f in *; do array[$i]="$f" ((i++)) done
Sie können dann zB die Dateien einzeln auflisten (in diesem Fall in umgekehrter Reihenfolge):
for ((i = $i - 1; i >= 0; i--)); do ls -al "${array[$i]}" done
Diese Seite enthält ein schönes Beispiel. Weitere Informationen finden Sie in Kapitel 26 im Advanced Bash-Scripting Guide .
quelle
Vermeiden Sie Xargs, wenn Sie können:
man ruby | less -p 777 IFS=$'\777' #array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) ) array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) ) echo ${#array[@]} printf "%s\n" "${array[@]}" | nl echo "${array[0]}" IFS=$' \t\n'
quelle
\777
?Ich bin neu, aber ich glaube, dass dies eine Antwort ist; hoffe es hilft jemandem:
STYLE="$HOME/.fluxbox/styles/" declare -a array1 LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f` echo $LISTING array1=( `echo $LISTING`) TAR_SOURCE=`echo ${array1[@]}` #tar czvf ~/FluxieStyles.tgz $TAR_SOURCE
quelle
Dies ähnelt der Version von Stephan202, aber die Dateien (und Verzeichnisse) werden auf einmal in einem Array abgelegt. Die
for
Schleife hier ist nur, um "nützliche Dinge zu tun":files=(*) # put files in current directory into an array i=0 for file in "${files[@]}" do echo "File ${i}: ${file}" # do something useful let i++ done
Um eine Zählung zu erhalten:
echo ${#files[@]}
quelle
Alte Frage, aber niemand schlug diese einfache Methode vor, also dachte ich, ich würde es tun. Zugegeben, wenn Ihre Dateinamen eine ETX haben, löst dies Ihr Problem nicht, aber ich vermute, dass es für jedes reale Szenario geeignet ist. Der Versuch, null zu verwenden, scheint gegen die Standardregeln für die IFS-Behandlung zu verstoßen. Würzen Sie nach Ihrem Geschmack mit Suchoptionen und Fehlerbehandlung.
savedFS="$IFS" IFS=$'\x3' filenames=(`find wherever -printf %p$'\x3'`) IFS="$savedFS"
quelle
Gordon Davissons Antwort ist großartig für Bash. Für zsh-Benutzer gibt es jedoch eine nützliche Verknüpfung:
Platzieren Sie zuerst Ihren String in einer Variablen:
A="$(find /tmp -type f -print0)"
Teilen Sie diese Variable als Nächstes auf und speichern Sie sie in einem Array:
B=( ${(s/^@/)A} )
Es gibt einen Trick:
^@
ist der NUL-Charakter. Dazu müssen Sie Strg + V gefolgt von Strg + @ eingeben.Sie können überprüfen, ob jeder Eintrag von $ B den richtigen Wert enthält:
for i in "$B[@]"; echo \"$i\"
Sorgfältige Leser können feststellen, dass der Aufruf eines
find
Befehls in den meisten Fällen mithilfe der**
Syntax vermieden werden kann . Zum Beispiel:quelle
Bash war noch nie gut im Umgang mit Dateinamen (oder wirklich jedem Text), da Leerzeichen als Listenbegrenzer verwendet werden.
Ich würde empfehlen, stattdessen Python mit der sh- Bibliothek zu verwenden.
quelle