Bash-Array mit Leerzeichen in Elementen


150

Ich versuche, ein Array in Bash der Dateinamen von meiner Kamera aus zu erstellen:

FILES=(2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg)

Wie Sie sehen können, befindet sich in der Mitte jedes Dateinamens ein Leerzeichen.

Ich habe versucht, jeden Namen in Anführungszeichen zu setzen und mit einem Backslash aus dem Leerzeichen zu entkommen, was jedoch nicht funktioniert.

Wenn ich versuche, auf die Array-Elemente zuzugreifen, wird der Raum weiterhin als Elementbegrenzer behandelt.

Wie kann ich die Dateinamen mit einem Leerzeichen im Namen richtig erfassen?


Haben Sie versucht, die Dateien auf altmodische Weise hinzuzufügen? Wie FILES[0] = ...? (Edit: Ich habe es gerade getan; funktioniert nicht. Interessant).
Dan Fego


Alle Antworten hier sind für mich mit Cygwin zusammengebrochen. Es macht seltsame Dinge, wenn es Leerzeichen in Dateinamen gibt, Punkt. Ich arbeite daran, indem ich ein "Array" in einer Textdatei erstelle, in dem alle Elemente aufgelistet sind, mit denen ich arbeiten möchte, und über die Zeilen in der Datei iteriere: Die Formatierung wird mit den beabsichtigten Backticks hier um den Befehl in Klammern herum durcheinander gebracht: IFS = ""; Array = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); für Element in $ {array [@]}; echo $ element; fertig
Alex Hall

Antworten:


121

Ich denke, das Problem könnte teilweise darin liegen, wie Sie auf die Elemente zugreifen. Wenn ich ein einfaches mache for elem in $FILES, habe ich das gleiche Problem wie Sie. Wenn ich jedoch wie folgt über seine Indizes auf das Array zugreife, funktioniert es, wenn ich die Elemente entweder numerisch oder mit Escapezeichen hinzufüge:

for ((i = 0; i < ${#FILES[@]}; i++))
do
    echo "${FILES[$i]}"
done

Jede dieser Erklärungen von $FILESsollte funktionieren:

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

oder

FILES=("2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg")

oder

FILES[0]="2011-09-04 21.43.02.jpg"
FILES[1]="2011-09-05 10.23.14.jpg"
FILES[2]="2011-09-09 12.31.16.jpg"
FILES[3]="2011-09-11 08.43.12.jpg"

6
Beachten Sie, dass Sie doppelte Anführungszeichen verwenden sollten, wenn Sie die Array-Elemente verwenden (z echo "${FILES[$i]}". B. ). Es spielt keine Rolle echo, aber es wird für alles, was es als Dateinamen verwendet.
Gordon Davisson

26
Es ist nicht erforderlich, die Indizes zu durchlaufen, wenn Sie die Elemente mit durchlaufen können for f in "${FILES[@]}".
Mark Edgar

10
@MarkEdgar Ich habe Probleme mit for f in $ {FILES [@]}, wenn die Array-Mitglieder Leerzeichen haben. Es scheint, dass das gesamte Array erneut interpretiert wird, wobei die Leerzeichen Ihre vorhandenen Mitglieder in zwei oder mehr Elemente aufteilen. Es scheint, dass die "" sehr wichtig sind
Michael Shaw

1
Was macht das scharfe ( #) Symbol in der for ((i = 0; i < ${#FILES[@]}; i++))Anweisung?
Michal Vician

4
Ich antwortete , das vor sechs Jahren , aber ich glaube , es ist das bekommen Zählung der Anzahl der Elemente im Array FILES.
Dan Fego

91

Es muss etwas falsch sein, wie Sie auf die Elemente des Arrays zugreifen. So geht's:

for elem in "${files[@]}"
...

Aus der Bash-Manpage :

Auf jedes Element eines Arrays kann mit $ {name [tiefgestellt]} verwiesen werden. ... Wenn der Index @ oder * ist, wird das Wort auf alle Mitglieder des Namens erweitert. Diese Indizes unterscheiden sich nur, wenn das Wort in doppelten Anführungszeichen steht. Wenn das Wort in doppelte Anführungszeichen gesetzt ist, wird $ {name [*]} zu einem einzelnen Wort erweitert, wobei der Wert jedes Array- Elements durch das erste Zeichen der IFS-Spezialvariablen getrennt wird, und $ {name [@]} erweitert jedes Element von Name zu einem separaten Wort .

Natürlich sollten Sie beim Zugriff auf ein einzelnes Mitglied auch doppelte Anführungszeichen verwenden

cp "${files[0]}" /tmp

3
Die sauberste und eleganteste Lösung in diesem Bündel sollte jedoch wiederholen, dass jedes im Array definierte Element in Anführungszeichen gesetzt werden sollte.
Außenseiter

Während Dan Fegos Antwort effektiv ist, ist dies die idiomatischere Art, mit Leerzeichen in den Elementen umzugehen.
Daniel Zhang

3
Aus anderen Programmiersprachen stammend, ist die Terminologie aus diesem Auszug wirklich schwer zu verstehen. Außerdem ist die Syntax verwirrend. Ich wäre Ihnen sehr dankbar, wenn Sie noch etwas näher darauf eingehen könnten. Besondersexpands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22

1
Ja, stimmen Sie zu, dass die doppelten Anführungszeichen es lösen, und dies ist besser als andere Lösungen. Zur weiteren Erklärung - den meisten anderen fehlen nur die doppelten Anführungszeichen. Sie haben das richtige:, for elem in "${files[@]}"während sie haben for elem in ${files[@]}- so verwirren die Leerzeichen die Erweiterung und für Versuche, auf den einzelnen Wörtern zu laufen.
Arntg

Dies funktioniert nicht für mich in macOS 10.14.4, das "GNU bash, Version 3.2.57 (1) -release (x86_64-apple-darwin18)" verwendet. Vielleicht ein Fehler in der älteren Version von Bash?
Mark Ribau

43

Sie müssen IFS verwenden, um das Leerzeichen als Elementtrennzeichen anzuhalten.

FILES=("2011-09-04 21.43.02.jpg"
       "2011-09-05 10.23.14.jpg"
       "2011-09-09 12.31.16.jpg"
       "2011-09-11 08.43.12.jpg")
IFS=""
for jpg in ${FILES[*]}
do
    echo "${jpg}"
done

Wenn Sie auf Basis von trennen möchten. dann mache einfach IFS = "." Hoffe es hilft dir :)


3
Ich musste das IFS = "" vor der Array-Zuweisung verschieben, aber dies ist die richtige Antwort.
Rob

Ich verwende mehrere Arrays, um Informationen zu analysieren, und ich werde den Effekt haben, dass IFS = "" nur in einem von ihnen funktioniert. Sobald ich IFS = "" verwende, hören alle anderen Arrays auf, entsprechend zu analysieren. Irgendwelche Hinweise dazu?
Paulo Pedroso

Paulo, sehen Sie hier eine andere Antwort, die für Ihren Fall besser sein könnte: stackoverflow.com/a/9089186/1041319 . Habe IFS = "" noch nicht ausprobiert und es scheint es elegant zu lösen - aber Ihr Beispiel zeigt, warum in einigen Fällen Probleme auftreten können. Es ist möglicherweise möglich, IFS = "" in einer einzelnen Zeile zu setzen, aber es ist möglicherweise immer noch verwirrender als die andere Lösung.
Arntg

Es hat auch bei Bash funktioniert. Danke @Khushneet Ich habe eine halbe Stunde lang danach gesucht ...
csonuryilmaz

Großartig, nur Antwort auf dieser Seite, die funktioniert hat. Aber ich musste auch die IFS="" vor der Array-Konstruktion verschieben .
pkamb

13

Ich stimme anderen zu, dass es wahrscheinlich ist, wie Sie auf die Elemente zugreifen, die das Problem sind. Das Zitieren der Dateinamen in der Array-Zuordnung ist korrekt:

FILES=(
  "2011-09-04 21.43.02.jpg"
  "2011-09-05 10.23.14.jpg"
  "2011-09-09 12.31.16.jpg"
  "2011-09-11 08.43.12.jpg"
)

for f in "${FILES[@]}"
do
  echo "$f"
done

Wenn Sie ein Array des Formulars "${FILES[@]}"in doppelte Anführungszeichen setzen, wird das Array in ein Wort pro Arrayelement aufgeteilt. Darüber hinaus wird kein Wort geteilt.

Die Verwendung "${FILES[*]}"hat auch eine besondere Bedeutung, verbindet jedoch die Array-Elemente mit dem ersten Zeichen von $ IFS, was zu einem Wort führt, was wahrscheinlich nicht das ist, was Sie wollen.

Wenn Sie ein Bare ${array[@]}oder ein ${array[*]}Subjekt als Ergebnis dieser Erweiterung für eine weitere Wortaufteilung verwenden, erhalten Sie Wörter, die auf Leerzeichen (und alles andere in $IFS) aufgeteilt sind, anstatt auf ein Wort pro Array-Element.

Die Verwendung einer for-Schleife im C-Stil ist ebenfalls in Ordnung und vermeidet die Sorge um die Wortteilung, wenn Sie sich nicht sicher sind:

for (( i = 0; i < ${#FILES[@]}; i++ ))
do
  echo "${FILES[$i]}"
done

3

Flucht funktioniert.

#!/bin/bash

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

echo ${FILES[0]}
echo ${FILES[1]}
echo ${FILES[2]}
echo ${FILES[3]}

Ausgabe:

$ ./test.sh
2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg

Das Zitieren der Zeichenfolgen erzeugt ebenfalls die gleiche Ausgabe.


3

Wenn Sie Ihr Array wie folgt hatten: #! / Bin / bash

Unix[0]='Debian'
Unix[1]="Red Hat"
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${Unix[@]});
    do echo $i;
done

Sie würden bekommen:

Debian
Red
Hat
Ubuntu
Suse

Ich weiß nicht warum, aber die Schleife zerlegt die Leerzeichen und fügt sie als einzelnes Element ein, selbst wenn Sie sie mit Anführungszeichen umgeben.

Um dies zu umgehen, rufen Sie die Indizes auf, anstatt die Elemente im Array aufzurufen. Dabei wird die vollständige Zeichenfolge verwendet, die in Anführungszeichen eingeschlossen ist. Es muss in Anführungszeichen gesetzt werden!

#!/bin/bash

Unix[0]='Debian'
Unix[1]='Red Hat'
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${!Unix[@]});
    do echo ${Unix[$i]};
done

Dann erhalten Sie:

Debian
Red Hat
Ubuntu
Suse

2

Nicht gerade eine Antwort auf das Zitat- / Fluchtproblem der ursprünglichen Frage, aber wahrscheinlich etwas, das für die Operation tatsächlich nützlicher gewesen wäre:

unset FILES
for f in 2011-*.jpg; do FILES+=("$f"); done
echo "${FILES[@]}"

Wo natürlich der Ausdruck an die spezifische Anforderung angepasst werden müsste (z. B. *.jpgfür alle oder 2001-09-11*.jpgnur für die Bilder eines bestimmten Tages).


0

Eine andere Lösung ist die Verwendung einer "while" -Schleife anstelle einer "for" -Schleife:

index=0
while [ ${index} -lt ${#Array[@]} ]
  do
     echo ${Array[${index}]}
     index=$(( $index + 1 ))
  done

0

Wenn Sie sich nicht auf die Verwendung beschränken bash, ist die unterschiedliche Behandlung von Leerzeichen in Dateinamen einer der Vorteile der Fischschale . Stellen Sie sich ein Verzeichnis vor, das zwei Dateien enthält: "a b.txt" und "b c.txt". Hier ist eine vernünftige Vermutung für die Verarbeitung einer Liste von Dateien, die mit einem anderen Befehl generiert wurden bash, die jedoch aufgrund von Leerzeichen in Dateinamen fehlschlägt:

# bash
$ for f in $(ls *.txt); { echo $f; }
a
b.txt
b
c.txt

Mit fishist die Syntax nahezu identisch, aber das Ergebnis ist das, was Sie erwarten würden:

# fish
for f in (ls *.txt); echo $f; end
a b.txt
b c.txt

Es funktioniert anders, weil fish die Ausgabe von Befehlen auf Zeilenumbrüche und nicht auf Leerzeichen aufteilt.

Wenn Sie einen Fall haben, in dem Sie Leerzeichen anstelle von Zeilenumbrüchen aufteilen möchten, fishhaben Sie eine gut lesbare Syntax dafür:

for f in (ls *.txt | string split " "); echo $f; end

0

Ich habe den IFS- Wert und das Rollback zurückgesetzt, wenn ich fertig war.

# backup IFS value
O_IFS=$IFS

# reset IFS value
IFS=""

FILES=(
"2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg"
)

for file in ${FILES[@]}; do
    echo ${file}
done

# rollback IFS value
IFS=${O_IFS}

Mögliche Ausgabe von der Schleife:

2011-09-04 21.43.02.jpg

2011-09-05 10.23.14.jpg

2011-09-09 12.31.16.jpg

2011-09-11 08.43.12.jpg

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.