2013-10-04 11 views
6

Sto provando a usare for nella shell per iterare su nomi di file con spazi. Ho letto in un stackoverflow question che potrebbe essere utilizzato IFS=$'\n' e sembra funzionare correttamente. Ho quindi letto in a comment to one of the answers che per renderlo posix compatibile per shell diverse da bash si potrebbe usare IFS=$(echo -e '\n').

Il primo funziona ma il secondo no. Il commento è aumentato di più volte. Ho controllato l'output e la variabile non sembra contenere la nuova riga.

#!/bin/sh 

IFS=$(echo -e '\n') 
echo -n "$IFS" | od -t x1 
for file in `printf 'one\ntwo two\nthree'`; do 
    echo "Found $file" 
done 

echo 

IFS=$'\n' 
echo -n "$IFS" | od -t x1 
for file in `printf 'one\ntwo two\nthree'`; do 
    echo "Found $file" 
done 


L'output mostra il separatore di campo mancante per primo:

0000000 
Found one 
two two 
three 


Ma è adeguata al secondo:

0000000 0a 
0000001 
Found one 
Found two two 
Found three 


Ho trovato un domanda ha risposto che può o non può essere un duplicato della mia domanda:
linux - When setting IFS to split on newlines, why is it necessary to include a backspace? - Stack Overflow

Esiste un pericolo per fare ciò che è discusso qui, IFS=$(echo -en '\n\b')? Perché quello aggiunge uno \b a IFS (ho controllato). Può un \b verificarsi in un nome di file? Non voglio che il mio codice divida per errore un nome di file.

Hai anche qualche consiglio su come gestire correttamente il ripristino di IFS dopo il ciclo for? Dovrei archiviarlo in una variabile temporanea, o eseguire IFS e l'istruzione for in una subshell? (E come fare?) Grazie

risposta

6

Aggiornamento - cambiando il mio pseudo-commento a una vera risposta.

Penso che this answer dovrebbe spiegare il comportamento che si sta vedendo. In particolare, gli operatori di sostituzione di comandi $() e i backtick elimineranno le righe nuove finali dall'output del comando. Tuttavia, l'assegnazione diretta nel secondo esempio non esegue alcuna sottomissione di comando, quindi funziona come previsto.

Quindi, ho paura di dire che penso che il commento in aumento a cui fai riferimento sia errato.


Penso che il modo più sicuro per ripristinare IFS sia impostarlo in una subshell. Tutto quello che dovete fare è mettere i comandi rilevanti tra parentesi ():

(
    IFS=$'\n' 
    echo -n "$IFS" | od -t x1 
    for file in `printf 'one\ntwo two\nthree'`; do 
     echo "Found $file" 
    done 
) 

Naturalmente invocando una subshell incorre un piccolo ritardo, quindi le prestazioni deve essere considerato se questo deve essere ripetuto molte volte.


Per inciso, essere molto attenti, i nomi dei file possono contenere sia \ b e \ n. Penso che solo gli unici caratteri che non possono contenere siano la barra e \ 0. Almeno questo è quello che dice su questo wikipedia article.

$ touch $'12345\b67890' 
$ touch "new 
> line" 
$ ls --show-control-chars 
123467890 new 
line 
$ 
4

Dal nuove righe vengono eliminati dalla sostituzione di comando, come @DigitalTrauma scritto correttamente, è necessario utilizzare un conforme a POSIX modo diverso,.

IFS=' 
' 

scrive una nuova riga e la assegna ad essa. Semplice ed efficace.

Se non ti piacciono le assegnazioni che si estendono su due linee, è possibile utilizzare printf come alternativa.

IFS="$(printf '\n')" 
+0

Grazie per le informazioni che mi terrò a mente – user2672807

+0

Si prega di rimuovere il secondo esempio in quanto non è pulito: 'IFS = "$ (printf '\ n +')"' '$ echo - n "$ IFS" | xxd' '00000000: 0a2b' <- Questo aggiunge' + 'anche a IFS. –

+0

@TomHale Ho rimosso il + che non era necessario –

Problemi correlati