Domanda

Ho uno script bash shell che scorre in tutte le directory figlio (ma non nei file) di una determinata directory. Il problema è che alcuni dei nomi di directory contengono spazi.

Ecco i contenuti della mia directory 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/Cherry
Hill
test/Edison 
test/New
York
City
test/Philadelphia

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

Ho provato a citare i nomi dei file, in questo modo:

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

ma inutilmente.

Deve esserci un modo semplice per farlo.


Le risposte di seguito sono fantastiche. Ma per renderlo più complicato, non sempre voglio usare le directory elencate nella mia directory di test. A volte, invece, desidero passare i nomi delle directory come parametri della riga di comando.

Ho preso il suggerimento di Charles di impostare l'IFS e ho pensato a quanto segue:

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

e questo funziona bene a meno che non ci siano spazi negli argomenti della riga di comando (anche se questi argomenti sono citati). Ad esempio, chiamando lo script in questo modo: test.sh "Cherry Hill" "New York City" produce il seguente output:

Cherry
Hill
New
York
City
È stato utile?

Soluzione

Innanzitutto, non farlo in questo modo. L'approccio migliore è utilizzare find -exec correttamente:

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

L'altro approccio sicuro consiste nell'utilizzare l'elenco con terminazione NUL, sebbene ciò richieda il supporto per la ricerca -print0:

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

Puoi anche popolare un array da find e passarlo successivamente:

# 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 la tua ricerca non supporta IFS, il tuo risultato non è sicuro - il seguito non si comporterà come desiderato se esistono file contenenti nuove righe nei loro 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 non si intende utilizzare uno dei precedenti, un terzo approccio (meno efficiente in termini di tempo e utilizzo della memoria, in quanto legge l'intero output del sottoprocesso prima di eseguire la suddivisione in parole) consiste nell'utilizzare un set -f variabile che non contiene il carattere spazio. Disattiva il globbing ([]) per impedire l'espansione di stringhe contenenti caratteri glob come *, ? o $@:

# 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 caso dei parametri della riga di comando, dovresti usare gli array se la tua shell li supporta (cioè è ksh, bash o zsh):

# this is safe
for d in "$@"; do
  printf '%s\n' "$d"
done

manterrà la separazione. Si noti che il preventivo (e l'uso di $* anziché <=>) è importante. Le matrici possono essere popolate anche in altri modi, come le espressioni glob:

# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
  printf '%s\n' "$d"
done

Altri suggerimenti

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

Tuttavia, non funziona se il nome file contiene nuove righe. Quanto sopra è l'unica soluzione che conosco quando in realtà si desidera avere il nome della directory in una variabile. Se vuoi solo eseguire qualche comando, usa xargs.

find . -type d -print0 | xargs -0 echo 'The directory is: '

Ecco una semplice soluzione che gestisce le schede e / o gli spazi bianchi nel nome del file. Se devi trattare con altri strani personaggi nel nome del file come newline, scegli un'altra risposta.

La directory di prova

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

Il codice da inserire nelle directory

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

Il nome del file deve essere citato ("$f") se utilizzato come argomento. Senza virgolette, gli spazi fungono da separatore di argomenti e più argomenti vengono dati al comando invocato.

E l'output:

test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia

Questo è estremamente complicato in Unix standard, e la maggior parte delle soluzioni si sporca di newline o di qualche altro personaggio. Tuttavia, se si utilizza il set di strumenti GNU, è possibile sfruttare l'opzione find -print0 e utilizzare xargs con l'opzione corrispondente -0 (meno-zero). Esistono due caratteri che non possono apparire in un semplice nome file; quelli sono barra e NUL '\ 0'. Ovviamente, la barra appare nei nomi dei percorsi, quindi la soluzione GNU di usare un NUL '\ 0' per segnare la fine del nome è ingegnosa e infallibile.

Perché non semplicemente mettere

IFS='\n'

davanti al comando for? Questo cambia il separatore di campo da & Lt; Spazio gt &; Lt &; Tab GT &; Lt &; Newline gt &; a solo < Newline gt &;

find . -print0|while read -d $'\0' file; do echo "$file"; done

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 abbastanza?
Idea tratta da http://www.cyberciti.biz/ suggerimenti / movimentazione-nomi di file-con-spazi-in-bash.html

Non archiviare elenchi come stringhe; archiviarli come array per evitare tutta questa confusione delimitatore. Ecco uno script di esempio che funzionerà su tutte le sottodirectory di test o sull'elenco fornito sulla sua riga 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=("$@")
fi
# now loop through dirlist, operating on each one
for dir in "${dirlist[@]}"; do
        printf "Directory: %s\n" "$dir"
done

Ora proviamo questo in una directory di test con una curva o due lanciati:

$ 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

È possibile utilizzare IFS (separatore di campo interno) nel tempo usando:

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

ps se riguarda solo lo spazio nell'input, allora alcune doppie virgolette hanno funzionato senza problemi per me ...

read artist;

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

Per aggiungere a ciò Jonathan detto: utilizzare l'opzione -print0 per find insieme a xargs come segue:

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

Questo eseguirà il comando command con gli argomenti appropriati; le directory con spazi al loro interno verranno opportunamente quotate (cioè verranno passate come un argomento).

#!/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 sopra convertirà i file .mov in .avi. I file .mov sono in diverse cartelle e i nomi delle cartelle hanno anche spazi bianchi . Il mio script sopra convertirà i file .mov in file .avi nella stessa cartella stessa. Non so se ti aiuti i popoli.

Caso:

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

Cheers!

Doveva occuparsi anche degli spazi bianchi nei nomi dei percorsi. Quello che finalmente ho fatto è stato usare una 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
}

Converti l'elenco dei file in un array Bash. Questo utilizza l'approccio di Matt McClure per restituire un array da una funzione Bash: http: // notes-matthewlmcclure .blogspot.com / 2009/12 / ritorno-array-da-bash-funzione-v-2.html Il risultato è un modo per convertire qualsiasi input multilinea in un array Bash.

#!/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 sono presenti caratteri errati ed è un modo generale per convertire qualsiasi input in un array Bash. Lo svantaggio è che se l'input è lungo potresti superare i limiti di dimensione della riga di comando di Bash o utilizzare grandi quantità di memoria.

Gli approcci in cui il loop che alla fine sta funzionando nell'elenco ha anche il piping dell'elenco hanno lo svantaggio che leggere stdin non è facile (come chiedere input all'utente) e il loop è un nuovo processo, quindi potresti essere chiedersi perché le variabili impostate all'interno del ciclo non siano disponibili al termine del ciclo.

Non mi piace anche impostare IFS, può rovinare altro codice.

ho appena scoperto che ci sono alcune somiglianze tra la mia question e il tuo. Allo stesso modo, se si desidera passare argomenti nei comandi

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

per stamparli in ordine

for SOME_ARG in "$@"
do
    echo "$SOME_ARG";
done;

nota che $ @ è racchiuso tra virgolette, alcune note qui

Avevo bisogno dello stesso concetto per comprimere sequenzialmente più directory o file da una determinata cartella. Ho risolto usando awk per analizzare l'elenco da ls ed evitare il problema dello 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!!!"

che ne pensi?

find Downloads -type f | while read file; do printf "%q\n" "$file"; done

Bene, vedo troppe risposte complicate. Non voglio passare l'output di find utility o scrivere un loop, perché find ha & Quot; exec & Quot; opzione per questo.

Il mio problema era che volevo spostare tutti i file con estensione dbf nella cartella corrente e alcuni di essi contenevano spazi bianchi.

L'ho affrontato così:

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

Sembra molto semplice per me

Per me funziona, ed è praticamente " clean " ;:

for f in "$(find ./test -type d)" ; do
  echo "$f"
done

Ho appena avuto un semplice problema di variante ... Converti i file di .flv digitati in .mp3 (sbadiglio).

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

trova ricorsivamente tutti i file flash dell'utente Macintosh e trasformali in audio (copia, nessuna transcodifica) ... è come nel caso precedente, notando che la lettura invece del solo 'per file in ' scapperà.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top