Modo conforme a POSIX per lavorare con un elenco di nomi di file possibilmente con spazi bianchi


14

Ho visto le guide di scripting di Bash che suggeriscono l'uso dell'array per lavorare con nomi di file contenenti spazi bianchi. DashAsBinSh suggerisce tuttavia che gli array non sono portatili, quindi sto cercando un modo conforme a POSIX di lavorare con elenchi di nomi di file che possono contenere spazi bianchi.

Sto cercando di modificare lo script di esempio di seguito in modo che lo farebbe echo

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

Ecco la sceneggiatura

#!/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

Risposte:


8

Shell POSIX hanno una matrice: i parametri posizionali ( $1, $2ecc, collettivamente riferiti come "$@").

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

Questo è scomodo perché ce n'è solo uno e distrugge qualsiasi altro uso dei parametri posizionali. I parametri posizionali sono locali in una funzione, che a volte è una benedizione e talvolta una maledizione.

Se si garantisce che i nomi dei file non contengano nuove righe, è possibile utilizzare le nuove righe come separatore. Quando si espande la variabile, disattivare innanzitutto il globbing set -fe impostare l'elenco di caratteri di divisione del campo IFSin modo che contenga solo una nuova riga.

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

Con gli elementi nel tuo elenco separati da nuove righe, puoi utilizzare molti comandi di elaborazione del testo in modo utile, in particolare sort.

Ricorda di mettere sempre le virgolette doppie attorno alle sostituzioni variabili, tranne quando desideri esplicitamente che si verifichi la divisione del campo (oltre al globbing, a meno che tu non l'abbia disattivato).


Buona risposta e spiegazione. Lo segnerò come accettato perché questo fa funzionare il sort | uniqpasso originale come previsto.
Eero Aaltonen,

5

Dal momento che la tua $INPUTvariabile usa newline come separatori, suppongo che i tuoi file non abbiano newline nei nomi. In quanto tale, sì, esiste un modo semplice per scorrere i file e preservare gli spazi bianchi.

L'idea è di usare la readshell integrata. Normalmente readsi dividerà su qualsiasi spazio bianco e quindi gli spazi lo spezzeranno. Ma puoi impostare IFS=$'\n'e si dividerà invece solo su newline. Quindi puoi iterare su ogni riga del tuo elenco.

Ecco la soluzione più piccola che potrei trovare:

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

Fondamentalmente invia "$ INPUT" al awkquale deduplica in base al nome del file (si divide /e quindi stampa la riga se l'ultimo elemento non è stato visto prima). Quindi, una volta che awk ha generato l'elenco dei percorsi dei file, usiamo while readper scorrere l'elenco.


$ checkbashisms bar.sh possibile bashism in bar.sh linea 14 (<<< qui stringa)
Eero Aaltonen

1
@EeroAaltonen Modificato per non utilizzare il herestring. Si noti tuttavia che con questa modifica, il whileciclo e quindi dostuffwithviene eseguito in una subshell. Quindi eventuali variabili o modifiche apportate alla shell in esecuzione andranno perse al termine del ciclo. L'unica alternativa è quella di usare una eredità completa, che non è poi così spiacevole, ma ho pensato che sarebbe stato preferibile.
Patrick,

Sto assegnando punti in base più alla leggibilità che alla piccolezza. Questo sicuramente funziona e già +1 per quello.
Eero Aaltonen,

IFS="\n"si divide su barra rovesciata e n caratteri. Ma dentro read file, non c'è divisione. IFS="\n"è ancora utile in quanto rimuove i caratteri vuoti da $ IFS che altrimenti sarebbero stati rimossi all'inizio e alla fine dell'input. Per leggere una riga, la sintassi canonica è IFS= read -r line, sebbene IFS=anything read -r line(purché tutto non contenga spazi) funzionerà anche.
Stéphane Chazelas,

oops. Non sono sicuro di come ho gestito quello. Fisso.
Patrick,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.