POSIX-konforme Arbeitsweise mit einer Liste von Dateinamen, möglicherweise mit Leerzeichen

14

Ich habe Bash-Skripthandbücher gesehen, die die Verwendung von Arrays für die Arbeit mit Dateinamen empfehlen, die Leerzeichen enthalten. DashAsBinSh schlägt jedoch vor, dass Arrays nicht portierbar sind. Daher suche ich nach einer POSIX-kompatiblen Möglichkeit, mit Listen von Dateinamen zu arbeiten, die möglicherweise Leerzeichen enthalten.

Ich bin auf der Suche, das folgende Beispielskript zu ändern, damit es würde echo

foo/target/a.jar
foo/target/b.jar
bar/target/lol whitespace.jar

Hier ist das Drehbuch

#!/usr/bin/env sh

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"
# this would be produced by a 'ls' command
# We can execute the ls within the script, if it helps

dostuffwith() { echo $1; };

F_LOCATIONS=$INPUT
ALL_FILES=$(for f in $F_LOCATIONS; do echo `basename $f`; done)
ALL_FILES=$(echo "$ALL_FILES" | sort | uniq)

for f in $ALL_FILES
do
    fpath=$(echo "$F_LOCATIONS" | grep -m1 $f)
    dostuffwith $fpath
done
Eero Aaltonen
quelle
Möglicherweise auch auf SO: stackoverflow.com/questions/6499486/…
Ciro Santilli am

Antworten:

8

POSIX Schalen weisen ein Array: die Positionsparameter ( $1, $2usw., kollektiv bezeichnet als "$@").

set -- 'foo/target/a.jar' 'foo/target/b.jar' 'bar/target/b.jar' 'bar/target/lol whitespace.jar'
set -- "$@" '/another/one at the end.jar'

for jar do
  dostuffwith "$jar"
done

Dies ist unpraktisch, da es nur eine gibt, und es zerstört jede andere Verwendung der Positionsparameter. Positionsparameter sind lokal für eine Funktion, die manchmal ein Segen und manchmal ein Fluch ist.

Wenn Ihre Dateinamen garantiert keine Zeilenumbrüche enthalten, können Sie Zeilenumbrüche als Trennzeichen verwenden. Wenn Sie die Variable erweitern, deaktivieren Sie zuerst das Globen mit set -fund stellen Sie die Liste der IFSfeldaufteilenden Zeichen so ein , dass sie nur eine neue Zeile enthält.

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"

set -f; IFS='
'                           # turn off variable value expansion except for splitting at newlines
for jar in $INPUT; do
  set +f; unset IFS
  dostuffwith "$jar"        # restore globbing and field splitting at all whitespace
done
set +f; unset IFS           # do it again in case $INPUT was empty

Wenn die Elemente in Ihrer Liste durch Zeilenumbrüche getrennt sind, können Sie insbesondere viele Textverarbeitungsbefehle sinnvoll verwenden sort.

Denken Sie daran, Variablensubstitutionen immer in doppelte Anführungszeichen zu setzen, es sei denn, Sie möchten explizit eine Feldaufteilung durchführen (und auch ein Globbing, sofern Sie dies nicht deaktiviert haben).

Gilles 'SO - hör auf böse zu sein'
quelle
Gute Antwort und Erklärung. Ich werde dies als akzeptiert markieren, da dadurch der ursprüngliche sort | uniqSchritt wie beabsichtigt funktioniert.
Eero Aaltonen
5

Da Ihre $INPUTVariable Zeilenumbrüche als Trennzeichen verwendet, gehe ich davon aus, dass Ihre Dateien keine Zeilenumbrüche in den Namen enthalten. Daher gibt es eine einfache Möglichkeit, die Dateien zu durchlaufen und Leerzeichen beizubehalten.

Die Idee ist, die readeingebaute Shell zu verwenden. Normalerweise readteilen sich Leerzeichen in Leerzeichen auf und Leerzeichen brechen sie auf. Aber Sie können festlegen, IFS=$'\n'und es wird stattdessen nur in Zeilenumbrüchen aufgeteilt. So können Sie jede Zeile in Ihrer Liste durchlaufen.

Hier ist die kleinste Lösung, die ich finden konnte:

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"

dostuffwith() {
    echo "$1"
}

echo "$INPUT" | awk -F/ '{if (!seen[$NF]++) print }' | \
while IFS=$'\n' read file; do
  dostuffwith "$file"
done

Grundsätzlich sendet es "$ INPUT" an awkdie Deduplikate basierend auf dem Dateinamen (es teilt sich auf /und druckt dann die Zeile, wenn das letzte Element noch nicht gesehen wurde). Sobald awk die Liste der Dateipfade erstellt hat, while readdurchlaufen wir die Liste.

Patrick
quelle
$ checkbashisms bar.sh mögliches bashism in bar.sh zeile 14 (<<< hier string)
Eero Aaltonen
1
@ EeroAaltonen Es wurde geändert, um den Herestring nicht zu verwenden. Beachten Sie jedoch, dass mit dieser Änderung die whileSchleife und damit dostuffwithin einer Subshell ausgeführt wird. So gehen alle Variablen oder Änderungen an der laufenden Shell verloren, wenn die Schleife abgeschlossen ist. Die einzige Alternative ist die Verwendung eines vollständigen Heredocs, was nicht so unangenehm ist, aber ich dachte, dies wäre vorzuziehen.
Patrick
Ich vergebe Punkte, die eher auf der Lesbarkeit als auf der Kleinheit beruhen. Das funktioniert sicher und schon +1 dafür.
Eero Aaltonen
IFS="\n"Aufteilung auf Backslash und n Zeichen. Aber in read filegibt es keine Spaltung. IFS="\n"ist immer noch nützlich, da es die leeren Zeichen aus $ IFS entfernt, die andernfalls am Anfang und Ende der Eingabe entfernt worden wären. Um eine Zeile zu lesen, die kanonische Syntax ist IFS= read -r line, obwohl IFS=anything read -r line(vorausgesetzt , alles enthält keine Leerzeichen) wird auch funktionieren.
Stéphane Chazelas
Hoppla. Ich bin mir nicht sicher, wie ich das geschafft habe. Fest.
Patrick