Angesichts dieser Dateinamen:
$ ls -1
file
file name
otherfile
bash
Selbst mit eingebettetem Whitespace ist das völlig in Ordnung:
$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?
Manchmal möchte ich jedoch möglicherweise nicht mit jeder Datei arbeiten oder sogar nur mit der Datei, in der $PWD
die Datei gespeichert ist find
.
$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name
Ich versuche, eine Whispace-sichere Version dieses Scriptlets zu erstellen, die die Ausgabe von find
und Präsentation in select
:
$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file
Dies explodiert jedoch mit Leerzeichen in den Dateinamen:
$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file 3) name 5) ./directory/file
2) ./file 4) ./directory/file 6) name
Normalerweise würde ich das umgehen, indem ich herumspiele IFS
. Jedoch:
$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
Was ist die Lösung dafür?
bash
text-processing
whitespace
select
DopeGhoti
quelle
quelle
find
Fähigkeit verwenden, einem bestimmten Dateinamen zu entsprechen, können Sieselect file in **/file*
(nach dem Einstellenshopt -s globstar
) einfachbash
4 oder höher verwenden.Antworten:
Wenn Sie nur Leerzeichen und Tabulatoren (keine eingebetteten Zeilenumbrüche) verwenden müssen, können Sie
mapfile
(oder sein Synonymreadarray
) verwenden, um in ein Array einzulesen, das z. B. angegeben wurdedann
Wenn Sie tun müssen , um Griff Zeilenumbrüche und Ihre
bash
Version bietet eine Null-separiertemapfile
1 , dann können Sie das ändernIFS= mapfile -t -d '' files < <(find . -type f -print0)
. Andernfalls stellen Siefind
mithilfe einerread
Schleife ein äquivalentes Array aus einer durch Nullen getrennten Ausgabe zusammen :1 Die
-d
Option wurdemapfile
inbash
Version 4.4 iirc hinzugefügtquelle
mapfile
ist eine neue für mich auch. Ein dickes Lob.while IFS= read
Version funktioniert bereits in Bash V3 (wichtig für Benutzer von MacOS).find -print0
Variante; meckern Sie , wenn Sie es nach einer bekanntermaßen falschen Version einfügen und nur dann beschreiben, wenn Sie wissen, dass Sie mit Zeilenumbrüchen umgehen müssen. Wenn man das Unerwartete nur an Orten behandelt, an denen es erwartet wird, wird man das Unerwartete überhaupt nicht behandeln.Diese Antwort bietet Lösungen für alle Arten von Dateien. Mit Zeilenumbrüchen oder Leerzeichen.
Es gibt Lösungen für aktuelle Bash sowie alte Bash- und sogar alte Posix-Shells.
Der in dieser Antwort unten aufgeführte Baum [1] wird für die Tests verwendet.
wählen
Es ist einfach,
select
mit einem Array zu arbeiten:Oder mit den Positionsparametern:
Das einzige wirkliche Problem besteht also darin, die "Liste der Dateien" (korrekt abgegrenzt) in einem Array oder in den Positionsparametern abzurufen. Weiter lesen.
Bash
Ich sehe das Problem, das Sie mit Bash melden, nicht. Bash kann in einem bestimmten Verzeichnis suchen:
Oder, wenn Sie eine Schleife mögen:
Beachten Sie, dass die obige Syntax mit jeder (vernünftigen) Shell korrekt funktioniert (zumindest nicht mit csh).
Die obige Syntax beschränkt sich nur darauf, in andere Verzeichnisse zu gelangen.
Aber bash könnte das tun:
Um nur einige Dateien auszuwählen (wie die, die auf file enden ), ersetzen Sie einfach das *:
robust
Wenn Sie einen „raum- platzieren sicher “ in den Titel, ich gehe davon aus, dass , was Sie meinten „war robust “.
Die einfachste Möglichkeit, Leerzeichen (oder Zeilenumbrüche) zu vermeiden, besteht darin, die Verarbeitung von Eingaben mit Leerzeichen (oder Zeilenumbrüchen) abzulehnen. Eine sehr einfache Möglichkeit, dies in der Shell zu tun, besteht darin, mit einem Fehler zu beenden, wenn ein Dateiname mit einem Leerzeichen erweitert wird. Es gibt mehrere Möglichkeiten, dies zu tun, aber die kompakteste (und posix) (jedoch auf einen Verzeichnisinhalt beschränkt, einschließlich suddirectories-Namen und Vermeiden von Punktdateien) ist:
Wenn die verwendete Lösung in einem dieser Punkte robust ist, entfernen Sie den Test.
In bash konnten Unterverzeichnisse sofort mit dem oben erläuterten ** getestet werden.
Es gibt verschiedene Möglichkeiten, Punktdateien einzuschließen. Die Posix-Lösung lautet:
finden
Wenn find aus irgendeinem Grund verwendet werden muss, ersetzen Sie das Trennzeichen durch eine NUL (0x00).
bash 4.4+
bash 2.05+
POSIXLY
Um eine gültige POSIX-Lösung zu erstellen, bei der find kein NUL-Trennzeichen hat und es kein
-d
(noch-a
) lesbares gibt, benötigen wir einen völlig anderen Ansatz.Wir müssen einen Komplex
-exec
aus find mit einem Aufruf an eine Shell verwenden:Oder wenn ein select benötigt wird (select ist Teil von bash, nicht sh):
[1] Dieser Baum (die \ 012 sind Zeilenumbrüche):
Könnte mit diesen beiden Befehlen erstellt werden:
quelle
Sie können eine Variable nicht vor ein Schleifenkonstrukt setzen, aber Sie können sie vor die Bedingung setzen. Hier ist das Segment aus der Manpage:
(Eine Schleife ist kein einfacher Befehl .)
Hier ist ein häufig verwendetes Konstrukt, das die Fehler- und Erfolgsszenarien demonstriert:
Leider sehe ich keine Möglichkeit, eine Änderung
IFS
in dasselect
Konstrukt einzubetten, obwohl sie sich auf die Verarbeitung eines assoziierten Objekts auswirkt$(...)
. Es gibt jedoch nichts zu verhindernIFS
, außerhalb der Schleife festgelegt zu werden:und es ist dieses Konstrukt, mit dem ich arbeiten kann
select
:Wenn defensiven Code schreiben würde ich empfehlen , dass die Klausel entweder in einer Subshell ausgeführt werden, oder
IFS
undSHELLOPTS
gespeichert und um den Block wieder hergestellt:quelle
IFS=$'\n'
sicher ist, ist unbegründet. Dateinamen können problemlos Newline-Literale enthalten.[0-9a-f]{24}
. Die TB der zur Unterstützung der Kundenabrechnung verwendeten Datensicherungen gingen verloren.select
Aufgrund seines Designs ist es für skriptbasierte Lösungen gedacht, daher sollte es immer für Edge-Cases ausgelegt sein.select
von einer Shell aus aufrufen, in der Sie die auszuführenden Befehle eingeben, sondern nur von einem Skript, in dem Sie auf eine von diesem Skript bereitgestellte Eingabeaufforderung antworten und in dem sich das Skript befindet Ausführen einer vordefinierten Logik (erstellt ohne Kenntnis der Dateinamen, mit denen gearbeitet wird) basierend auf dieser Eingabe.Vielleicht bin ich hier nicht zuständig, aber vielleicht können Sie mit so etwas anfangen, zumindest hat es keine Probleme mit dem Leerzeichen:
Beachten Sie zur Vermeidung möglicher falscher Annahmen, wie in den Kommentaren angegeben, dass der obige Code äquivalent ist zu:
quelle
read -d
ist eine clevere Lösung; Danke dafür.read -d $'\000'
ist genau identisch mitread -d ''
, aber für irreführende Leute über die Fähigkeiten von bash (was fälschlicherweise impliziert, dass es in der Lage ist, wörtliche NULs in Strings darzustellen). Führen Sie auss1=$'foo\000bar'; s2='foo'
, und versuchen Sie dann, eine Möglichkeit zu finden, zwischen den beiden Werten zu unterscheiden. (Eine zukünftige Version kann sich mit dem Befehlsersetzungsverhalten normalisieren, indem der gespeicherte Wert gleichgesetzt wirdfoobar
, aber das ist heute nicht der Fall.)