Come posso sfuggire uno spazio bianco in una bash loop lista?

Ho un bash script di shell che scorre in tutte le directory figlio (ma non i file) di una certa directory. Il problema è che alcuni nomi di directory che contengono spazi.

Ecco il contenuto della mia cartella di test:

$ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

E il codice che scorre attraverso le directory:

for f in `find test/* -type d`; do
  echo $f
done

Ecco l’output:

test/Baltimore 
test/Ciliegio 
Hill 
test/Edison 
test/Nuovo 
York 
Città 
test/Philadelphia 

Cherry Hill e New York City sono considerati come 2 o 3 voci separate.

Ho provato citando i nomi di file, in questo modo:

for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do
  echo $f
done

ma senza alcun risultato.

Ci deve essere un modo semplice per fare questo.


Le risposte qui di seguito sono grandi. Ma per rendere questo più complicata, non ho sempre voglia di utilizzare le directory elencate nella mia directory di prova. A volte ho voglia di passare i nomi di directory come parametri della riga di comando invece.

Ho preso Charles’ suggerimento di impostazione IFS e si avvicinò con la seguente:

dirlist="${@}"
(
  [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
  for d in $dirlist; do
    echo $d
  done
)

e questo funziona bene se non ci sono spazi di argomenti della riga di comando (anche se tali argomenti sono quotati). Per esempio, chiamare lo script come questo: test.sh "Cherry Hill" "New York City" produce il seguente output:

Ciliegio 
Hill 
Nuovo 
York 
Città 
  • re: modifica, list="[email protected]" completamente ignora il valore originale dell’elenco-ness, la compressione di una stringa. Si prega di seguire le pratiche per la mia risposta, esattamente come dato, ad un’assegnazione non è consigliato, ovunque esso; se si desidera passare un elenco di argomenti della riga di comando di un programma, è necessario raccogliere in un array, e di espandere la matrice direttamente.
InformationsquelleAutor MCS | 2008-11-19

 

20 Replies
  1. 103

    Prima, non farlo in quel modo. L’approccio migliore è quello di utilizzare find -exec correttamente:

    # this is safe
    find test -type d -exec echo '{}' +

    Altro metodo sicuro è quello di utilizzare NUL-terminato elenco, anche se questo richiede che il tuo supporto -print0:

    # this is safe
    while IFS= read -r -d '' n; do
      printf '%q\n' "$n"
    done < <(find test -mindepth 1 -type d -print0)

    È inoltre possibile compilare un array da trovare, e passare la matrice più tardi:

    # this is safe
    declare -a myarray
    while IFS= read -r -d '' n; do
      myarray+=( "$n" )
    done < <(find test -mindepth 1 -type d -print0)
    printf '%q\n' "${myarray[@]}" # printf is an example; use it however you want

    Se il tuo non supporta -print0, il risultato è quindi pericoloso — al di sotto non si comportano come desiderato se esistono i file contenenti caratteri nei nomi (che, sì, è legale):

    # this is unsafe
    while IFS= read -r n; do
      printf '%q\n' "$n"
    done < <(find test -mindepth 1 -type d)

    Se uno non ha intenzione di usare una di queste, un terzo approccio (meno efficiente in termini di tempo e di utilizzo della memoria, così come si legge l’intera produzione del sottoprocesso prima di fare parola di scissione) è quello di utilizzare un IFS variabile che non contengono il carattere di spazio. Disattivare la funzione glob (set -f) per evitare di stringhe contenente glob personaggi come [], * o ? dall’essere ampliato:

    # this is unsafe (but less unsafe than it would be without the following precautions)
    (
     IFS=$'\n' # split only on newlines
     set -f    # disable globbing
     for n in $(find test -mindepth 1 -type d); do
       printf '%q\n' "$n"
     done
    )

    Infine, per il parametro della riga di comando caso, si dovrebbe essere utilizzando matrici se la shell li supporta (cioè, è ksh, bash o zsh):

    # this is safe
    for d in "[email protected]"; do
      printf '%s\n' "$d"
    done

    di mantenere la separazione. Nota che il quoting (e l’uso di [email protected] piuttosto che $*) è importante. Le matrici possono essere compilati anche in altri modi, come glob espressioni:

    # this is safe
    entries=( test/* )
    for d in "${entries[@]}"; do
      printf '%s\n' "$d"
    done
    • non sapevo che il ‘+’ sapore per -exec. dolce
    • tho guarda come è anche possibile, come xargs, solo mettere gli argomenti alla fine di un determinato comando :/ che mi ha ostacolato, a volte
    • Penso -exec [nome] {} + è una distribuzione GNU e 4.4-BSD estensione. (Almeno, non appare su Solaris 8, e non credo che era in AIX 4.3 sia). Credo che il resto di noi può essere bloccato con tubazioni a xargs…
    • Non ho mai visto l’ $’\n’ sintassi prima. Come funziona? (Avrei pensato che sia IFS=’\n’ o IFS=”\n” potrebbe funzionare, ma non è.)
    • e ‘ sicuramente in Solaris 10, ho solo usato.
    • IFS=$’\n’ funziona abbastanza bene. probabilmente, meglio aggiungere un backup variabile prima di fare questo : IFSBACKUP=$IFS;IFS=$’\n’;…;IFS=$IFSBACKUP
    • Ultimo trucco era nuovo per me e molto utile, grazie!
    • È conforme agli standard, non è un’estensione, ma una abbastanza nuova aggiunta alla standard, e alcune piattaforme non sono coinvolti.
    • +1. Non spegnere il globbing bisogno di un set -f piuttosto che un set +f?
    • Abbastanza di destra. Corretto.
    • non c’è bisogno di backup il valore precedente quando (come qui) è fatto all’interno di una subshell; il valore viene ripristinato non appena il processo di chiusura.
    • altra cosa su IFSBACKUP=$IFS; : ...; IFS=$IFSBACKUP è che IFS essere annullata e IFS essere impostato su un valore vuoto sono due cose diverse (ex default $' \t\n' e il secondo non), e che il codice perde la distinzione. Vorrei suggerire di oIFS=${IFS:$' \t\n'}; in questo modo, quando si imposta IFS=$oIFS più tardi, non modifica il comportamento se il valore iniziale è stato annullato.

  2. 25
    find . -type d | while read file; do echo $file; done

    Tuttavia, non funziona se il nome del file contiene caratteri. Sopra è l’unica soluzione quando si vuole realmente avere il nome della directory in una variabile. Se si desidera eseguire un certo comando, utilizzare xargs.

    find . -type d -print0 | xargs -0 echo 'The directory is: '
    • Nessun bisogno di xargs, vedere trovare -exec … {} +
    • per un gran numero di file, xargs è molto più efficiente: si genera soltanto un processo. L’opzione-exec forcelle di un nuovo processo per ogni file, che può essere un ordine di grandezza più lento.
    • Mi piace xargs più. Questi due essenzialmente sembrano fare lo stesso per entrambi, mentre xargs ha più opzioni, come l’esecuzione in parallelo
    • Adamo, non che ‘+’ uno di aggregazione come molti nomi di file come possibile e poi esegue. ma non sono tali pulito funzioni come l’esecuzione in parallelo 🙂
    • giù le mani, che hanno \n nella loro dirnames comunque :p in pietra di loro ^^
    • persone che cercano di sfruttare le falle di sicurezza nel codice, per uno. Supponendo che la gente farà il sano di mente o cosa ragionevole è pericoloso.
    • +1 per il codice di sicurezza.
    • Si noti che se si vuole fare qualcosa con i nomi dei file, si sta andando ad avere per citare. E. g.: find . -type d | while read file; do ls "$file"; done
    • +1 per l’utilizzo di xargs, che comporta importanti vantaggi prestazionali rispetto all’uso di find-exec. L’uso di xargs batch file singolo comando esecuzioni e, facoltativamente, supporta l’esecuzione in parallelo su più core.
    • dosaggio è fornito anche da find -exec {} +, che è stato parte dello standard POSIX dal 2006.
    • per inciso, è stato sottolineato da litb in un precedente commento in questo thread, nel ’08.

  3. 21

    Qui è una soluzione semplice che gestisce schede e/o spazi nel nome del file. Se hai a che fare con altri caratteri strani nel nome del file, come i ritorni a capo, scegli un’altra risposta.

    Directory test

    ls -F test
    Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

    Il codice per andare nella directory

    find test -type d | while read f ; do
      echo "$f"
    done

    Il nome del file deve essere citato ( "$f" ), se usato come argomento. Senza virgolette, gli spazi di agire come separatore argomento e più sono argomenti per il comando invocato.

    E l’output:

    test/Baltimore
    test/Cherry Hill
    test/Edison
    test/New York City
    test/Philadelphia
    • grazie, questo ha funzionato per l’alias che ho creato per elencare la quantità di spazio di ogni directory nella cartella corrente è in uso, è stato il soffocamento su alcune cartelle con spazi nella precedente incarnazione. Questo funziona in zsh, ma alcune delle altre risposte non: alias duc='ls -d * | while read D; do du -sh "$D"; done;'
    • Se si utilizza zsh, si può anche fare questo: alias duc='du -sh *(/)'
    • Questo è ancora pieno di bug. Provare con un nome di file, per esempio, in una sequenza di tabulazione, o più spazi; si noti che le modifiche qualsiasi di questi in un unico spazio, perché non citando nella tua eco. E poi c’è il caso di nomi di file contenenti punto e a capo…
    • Ho provato con la scheda sequenze e più spazi. Funziona con le virgolette. Ho provato anche con i ritorni a capo e non lavoro a tutti. Ho aggiornato la risposta di conseguenza. Grazie per la precisazione.
    • Diritto — aggiunta di citazioni per il comando echo era quello che stavo ottenendo. Come per i ritorni a capo, è possibile effettuare tale lavoro utilizzando trova -print0 e IFS='' read -r -d '' f.
    • Grazie per la precisione, anche se mi terrò la risposta semplice, anche se non è infallibile.

  4. 7

    Questo è estremamente difficile in standard di Unix, e la maggior parte delle soluzioni eseguire fallo di caratteri o altri caratteri. Tuttavia, se si utilizza GNU set di strumenti, allora si può sfruttare il find opzione -print0 e utilizzare xargs con l’opzione corrispondente -0 (meno di zero). Ci sono due personaggi che non possono apparire in una semplice nome di file; questi sono slash e NUL ‘\0’. Ovviamente, la barra viene visualizzata in base al percorso, in modo che il GNU soluzione di utilizzare un NUL ‘\0’ per segnare la fine del nome è geniale e a prova di stupido.

  5. 4

    Perché non basta mettere

    IFS='\n'

    davanti al comando? Questo cambia il separatore di campo da < Spazio>< Scheda>< Newline> solo < Newline>

    • Deve essere IFS=$'\n', non IFS='\n', al lavoro.
  6. 4
    find . -print0|while read -d $'\0' file; do echo "$file"; done
    • -d $'\0' è esattamente lo stesso -d '' — perché bash usa NUL-terminato le stringhe, il primo carattere di una stringa vuota è un NUL, e per lo stesso motivo, NULs non può essere rappresentato all’interno di stringhe C a tutti.
  7. 4

    Io uso

    SAVEIFS=$IFS
    IFS=$(echo -en "\n\b")
    for f in $( find "$1" -type d ! -path "$1" )
    do
      echo $f
    done
    IFS=$SAVEIFS

    Non sarebbe sufficiente?

    Idea presa da http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html

    • suggerimento: è molto utile per le opzioni a riga di comando osascript (OS X AppleScript), dove gli spazi dividere un argomento in più dei parametri di cui solo uno è destinato
    • No, non è abbastanza. È inefficiente (a causa di un uso non necessario di $(echo ...)), non gestisce i nomi di file con glob espressioni correttamente, non gestisce i nomi di file che contengono $'\b' o $’\n’ caratteri correttamente, e inoltre converte più sedute di spazi vuoti in singoli caratteri di spazio vuoto sul lato di uscita a causa della non corretta citazione.
  8. 4

    Non conservare le liste di stringhe; memorizzarli come matrici per evitare tutto questo delimitatore di confusione. Ecco un esempio di script che ti sia di operare su tutte le sottodirectory di test o l’elenco fornito sulla linea di comando:

    #!/bin/bash
    if [ $# -eq 0 ]; then
            # if no args supplies, build a list of subdirs of test/
            dirlist=() # start with empty list
            for f in test/*; do # for each item in test/...
                    if [ -d "$f" ]; then # if it's a subdir...
                            dirlist=("${dirlist[@]}" "$f") # add it to the list
                    fi
            done
    else
            # if args were supplied, copy the list of args into dirlist
            dirlist=("[email protected]")
    fi
    # now loop through dirlist, operating on each one
    for dir in "${dirlist[@]}"; do
            printf "Directory: %s\n" "$dir"
    done

    Ora facciamo una prova su una directory di prova con una curva o due gettati in:

    $ ls -F test
    Baltimore/
    Cherry Hill/
    Edison/
    New York City/
    Philadelphia/
    this is a dirname with quotes, lfs, escapes: "\''?'?\e\n\d/
    this is a file, not a directory
    $ ./test.sh 
    Directory: test/Baltimore
    Directory: test/Cherry Hill
    Directory: test/Edison
    Directory: test/New York City
    Directory: test/Philadelphia
    Directory: test/this is a dirname with quotes, lfs, escapes: "\''
    '
    \e\n\d
    $ ./test.sh "Cherry Hill" "New York City"
    Directory: Cherry Hill
    Directory: New York City
    • Guardando indietro su questo: effettivamente c’è è una soluzione con POSIX sh: Si potrebbe riutilizzare il "[email protected]" array, aggiungendo ad esso con set -- "[email protected]" "$f".
  9. 4

    Si potrebbe utilizzare IFS (internal field separator) temporalmente utilizzando :

    OLD_IFS=$IFS     # Stores Default IFS
    IFS=$'\n'        # Set it to line break
    for f in `find test/* -type d`; do
        echo $f
    done
    
    $IFS=$OLD_IFS

    • Si prega di fornire una spiegazione.
    • IFS specificato che il separatore è, quindi il nome del file con uno spazio bianco non sarebbe troncato.
  10. 3

    ps se è solo su space in ingresso, quindi un po ‘ di doppie virgolette lavorato senza problemi per me…

    read artist;
    
    find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;
  11. 2

    Aggiungere a ciò che Jonathan detto: utilizza il -print0 opzione per find in collaborazione con xargs come segue:

    find test/* -type d -print0 | xargs -0 command

    Che eseguirà il comando command con le giuste argomentazioni, le directory con spazi saranno correttamente citato (cioè quando sarà passato come argomento).

  12. 1
    #!/bin/bash
    
    dirtys=()
    
    for folder in *
    do    
     if [ -d "$folder" ]; then    
        dirtys=("${dirtys[@]}" "$folder")    
     fi    
    done    
    
    for dir in "${dirtys[@]}"    
    do    
       for file in "$dir"/\*.mov   # <== *.mov
       do    
           #dir_e=`echo "$dir" | sed 's/[[:space:]]/\\\ /g'`   -- This line will replace each space into '\ '   
           out=`echo "$file" | sed 's/\(.*\)\/\(.*\)/\2/'`     # These two line code can be written in one line using multiple sed commands.    
           out=`echo "$out" | sed 's/[[:space:]]/_/g'`    
           #echo "ffmpeg -i $out_e -sameq -vcodec msmpeg4v2 -acodec pcm_u8 $dir_e/${out/%mov/avi}"    
           `ffmpeg -i "$file" -sameq -vcodec msmpeg4v2 -acodec pcm_u8 "$dir"/${out/%mov/avi}`    
       done    
    done

    Il codice precedente la conversione .i file mov .avi. L’ .i file mov sono in cartelle diverse e
    i nomi delle cartelle sono spazi bianchi troppo. Il mio script per convertire il .i file mov .avi file nella stessa cartella stessa. Non so se è di aiuto popoli.

    Caso:

    [[email protected] shell_tutorial]$ ls
    Chapter 01 - Introduction  Chapter 02 - Your First Shell Script
    [[email protected] shell_tutorial]$ cd Chapter\ 01\ -\ Introduction/
    [[email protected] Chapter 01 - Introduction]$ ls
    0101 - About this Course.mov   0102 - Course Structure.mov
    [[email protected] Chapter 01 - Introduction]$ ./above_script
     ... successfully executed.
    [[email protected] Chapter 01 - Introduction]$ ls
    0101_-_About_this_Course.avi  0102_-_Course_Structure.avi
    0101 - About this Course.mov  0102 - Course Structure.mov
    [[email protected] Chapter 01 - Introduction]$ CHEERS!

    Evviva!

    • echo "$name" | ... non funziona se name è -n, e come si comporta con i nomi di con la barra rovesciata-sequenze di escape dipende dal vostro attuazione — POSIX rende il comportamento di echo in questo caso esplicitamente definito (considerando che XSI-estensione POSIX rende espansione di barra rovesciata-sequenze di escape standard di comportamento definito, e sistemi GNU — compreso bash — senza POSIXLY_CORRECT=1 rompere standard POSIX, mediante l’attuazione di -e (considerando che la spec richiede echo -e di stampa -e in uscita). printf '%s\n' "$name" | ... è più sicuro.
  13. 1

    Avuto a che fare con gli spazi nei nomi di file, troppo. Alla fine ho fatto con la ricorsione e for item in /path/*:

    function recursedir {
        local item
        for item in "${1%/}"/*
        do
            if [ -d "$item" ]
            then
                recursedir "$item"
            else
                command
            fi
        done
    }
    • Non utilizzare il function parola chiave: rende il codice è incompatibile con POSIX sh, ma non ha altri scopi. Si può definire una funzione con recursedir() {, aggiungendo le due parentesi e rimuovere la parola chiave function, e questo sarà compatibile con tutti i sistemi POSIX-compliant conchiglie.
  14. 1

    Convertire il file in un elenco in un Bash array. Questo utilizza Matt McClure approccio per la restituzione di un array da una funzione Bash:
    http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html
    Il risultato è un modo per convertire qualsiasi multi-ingresso di linea per un Bash array.

    #!/bin/bash
    
    # This is the command where we want to convert the output to an array.
    # Output is: fileSize fileNameIncludingPath
    multiLineCommand="find . -mindepth 1 -printf '%s %p\\n'"
    
    # This eval converts the multi-line output of multiLineCommand to a
    # Bash array. To convert stdin, remove: < <(eval "$multiLineCommand" )
    eval "declare -a myArray=`( arr=(); while read -r line; do arr[${#arr[@]}]="$line"; done; declare -p arr | sed -e 's/^declare -a arr=//' ) < <(eval "$multiLineCommand" )`"
    
    for f in "${myArray[@]}"
    do
       echo "Element: $f"
    done

    Questo approccio sembra funzionare anche quando i cattivi sono presenti, ed è un modo generale di convertire qualsiasi ingresso a un Bash array. Lo svantaggio è che se l’ingresso è a lungo si potrebbe superare il Bash della riga di comando di limiti di dimensione, o utilizzare grandi quantità di memoria.

    Approcci in cui il ciclo è alla fine di lavorare in lista anche l’elenco reindirizzato hanno lo svantaggio che la lettura di stdin non è facile (come ad esempio chiedere all’utente per l’input), e il ciclo è un nuovo processo in modo che si può essere chiedendo perché le variabili, è possibile impostare all’interno del ciclo non sono disponibili dopo il ciclo termina.

    Anche io ho antipatia per impostazione IFS, può rovinare un altro codice.

    • Se si utilizza IFS='' read, sulla stessa linea, l’IFS impostazione è presente solo per il comando di lettura, e non scappare. Non c’è motivo di antipatia impostazione IFS in questo modo.
  15. 0

    appena scoperto che ci sono alcune somiglianze tra il mio domanda e la vostra. Aparrently se si desidera passare gli argomenti in comandi

    test.sh "Cherry Hill" "New York City"

    stamparli in ordine

    for SOME_ARG in "[email protected]"
    do
        echo "$SOME_ARG";
    done;

    notare il [email protected] è racchiuso tra virgolette doppie, alcune note qui

  16. 0

    Ho bisogno lo stesso concetto di comprimere in sequenza diverse cartelle o i file in una determinata cartella. Ho risolto utilizzando awk parsel elenco ls e per evitare il problema di spazio vuoto nel nome.

    source="/xxx/xxx"
    dest="/yyy/yyy"
    
    n_max=`ls . | wc -l`
    
    echo "Loop over items..."
    i=1
    while [ $i -le $n_max ];do
    item=`ls . | awk 'NR=='$i'' `
    echo "File selected for compression: $item"
    tar -cvzf $dest/"$item".tar.gz "$item"
    i=$(( i + 1 ))
    done
    echo "Done!!!"

    voi cosa ne pensate?

    • Penso che questo non funzionerà correttamente se i nomi di file con caratteri in loro. Forse si dovrebbe provare.
  17. 0

    Bene, vedo troppi complessi di risposte. Non voglio passare l’output di utilità di ricerca o di scrivere un ciclo , perché trovare è “exec” opzione per questo.

    Il mio problema era che volevo spostare tutti i file con estensione dbf la cartella corrente e alcuni di essi contenute spazio bianco.

    Ho affrontato così:

     find . -name \*.dbf -print0 -exec mv '{}'  . ';'

    Sembra molto semplice per me

  18. -3

    Per me funziona, ed è molto “pulito”:

    for f in "$(find ./test -type d)" ; do
      echo "$f"
    done
    • Ma questo è peggio. Le virgolette intorno l’trovare la causa di tutti i nomi di percorso per essere concatenati come una singola stringa. Modificare il echo a ls per vedere il problema.
  19. -4

    Era solo una semplice variante problema… Convertire i file digitato .flv .mp3 (sbadiglio).

    for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done

    in modo ricorsivo trovare tutti gli utenti Macintosh file flash e li trasformano in audio (copia, senza transcodifica) … è come il tempo sopra, rilevando che leggere invece che solo ” per il file in ‘ sfuggire.

    • Il read dopo in è ancora una parola nell’elenco che si sta scorrendo. Quello che hai postato è un po ‘ rotto versione di ciò che il richiedente aveva, che non funziona. Potrebbe essere destinato a postare qualcosa di diverso, ma probabilmente è coperta da altre risposte qui, comunque.

Lascia un commento