Nei casi sorprendentemente frequenti in cui ciò che è effettivamente necessario fare è in qualche modo elaborare tutte le linee non vuote all'interno di una variabile (incluso il conteggio), è possibile impostare IFS su una nuova riga e quindi utilizzare il meccanismo di suddivisione delle parole della shell per interrompere le linee non vuote a parte.
Ad esempio, ecco una piccola funzione di shell che somma le linee non vuote all'interno di tutti gli argomenti forniti:
lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)
Le parentesi, anziché le parentesi graffe, vengono utilizzate qui per formare il comando composto per il corpo della funzione. Questo fa sì che la funzione venga eseguita in una subshell in modo da non inquinare la variabile IFS del mondo esterno e l'impostazione di espansione del percorso su ogni chiamata.
Se vuoi iterare su righe non vuote puoi farlo in modo simile:
IFS='
'
set -f
for line in $lines
do
printf '[%s]\n' $line
done
Manipolare IFS in questo modo è una tecnica spesso trascurata, utile anche per fare cose come l'analisi di nomi di percorsi che potrebbero contenere spazi dall'input colonnare delimitato da tabulazioni. Tuttavia, è necessario essere consapevoli del fatto che la rimozione deliberata del carattere spazio solitamente incluso nell'impostazione predefinita di IFS di spazio-tab-newline può finire per disabilitare la divisione delle parole in luoghi in cui normalmente ci si aspetterebbe di vederlo.
Ad esempio, se stai usando le variabili per creare una riga di comando complicata per qualcosa del genere ffmpeg
, potresti voler includere -vf scale=$scale
solo quando la variabile scale
è impostata su qualcosa di non vuoto. Normalmente puoi farlo con ${scale:+-vf scale=$scale}
ma se IFS non include il suo solito carattere di spazio al momento dell'espansione di questo parametro, lo spazio tra -vf
e scale=
non verrà utilizzato come separatore di parole e ffmpeg
verrà passato tutto -vf scale=$scale
come un singolo argomento, che non capirà.
Per rimediare, faresti sia necessario assicurarsi IFS è stato set più normalmente prima di fare l' ${scale}
espansione, o fare due espansioni: ${scale:+-vf} ${scale:+scale=$scale}
. La suddivisione delle parole che la shell esegue nel processo di analisi iniziale delle righe di comando, al contrario della divisione che esegue durante la fase di espansione dell'elaborazione di tali righe di comando, non dipende dall'IFS.
Qualcos'altro che potrebbe valere la pena se stai per fare questo tipo di cose sarebbe la creazione di due variabili di shell globali per contenere solo una scheda e solo una nuova riga:
t=' '
n='
'
In questo modo puoi semplicemente includere $t
e $n
in espansioni in cui hai bisogno di schede e newline, piuttosto che sporcare tutto il tuo codice con spazi bianchi tra virgolette. Se preferisci evitare gli spazi bianchi citati del tutto in una shell POSIX che non ha altri meccanismi per farlo, printf
può aiutarti anche se hai bisogno di un po 'di armeggiare per aggirare la rimozione di nuove righe finali nelle espansioni di comandi:
nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}
A volte l'impostazione di IFS come se fosse una variabile d'ambiente per comando funziona bene. Ad esempio, ecco un ciclo che legge un percorso che può contenere spazi e un fattore di ridimensionamento da ciascuna riga di un file di input delimitato da tabulazioni:
while IFS=$t read -r path scale
do
ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt
In questo caso il read
builtin vede IFS impostato su solo una scheda, quindi non dividerà anche la linea di input che legge sugli spazi. Ma IFS=$t set -- $lines
non funziona: la shell si espande $lines
man mano che costruisce gli set
argomenti del builtin prima di eseguire il comando, quindi l'impostazione temporanea di IFS in un modo che si applica solo durante l'esecuzione del builtin stesso arriva troppo tardi. Questo è il motivo per cui i frammenti di codice che ho fornito hanno soprattutto impostato IFS in un passaggio separato e perché devono affrontare il problema di preservarlo.
wc -l
è esattamente equivalente all'originale:<<<$foo
aggiunge una nuova riga al valore di$foo
(anche se$foo
era vuoto). Spiego nella mia risposta perché questo potrebbe non essere stato ciò che si voleva, ma è ciò che è stato chiesto.