Domanda

Utilizzando find . -print0 sembra essere l'unico modo sicuro per ottenere un elenco di file in bash a causa della possibilità di nomi di file contenenti spazi, a capo, tra virgolette, ecc.

Comunque, sto attraversando un periodo difficile in realtà fare l'output di find utile all'interno di bash o con altre utilità della riga di comando. L'unico modo sono riuscito a sfruttare l'uscita è da una pipe con Perl, e cambiando IFS Perl null:

find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'

In questo esempio stampa il numero di file trovati, evitando il pericolo di nuove righe nei nomi dei file corrompere il conteggio, come accadrebbe con:

find . | wc -l

Come programmi riga più comando non supportano ingresso nullo delimitato, immagino la cosa migliore sarebbe catturare l'output di find . -print0 in una matrice bash, come ho fatto nelle Perl frammento sopra, e poi proseguire con il compito , qualunque essa sia.

Come posso fare questo?

Questo non funziona:

find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

Una questione molto più generale potrebbe essere: Come posso fare cose utili con elenchi di file in bash

?
È stato utile?

Soluzione

di Greg BashFAQ :

unset a i
while IFS= read -r -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done < <(find /tmp -type f -print0)

Si noti che il costrutto reindirizzamento utilizzato qui (cmd1 < <(cmd2)) è simile, ma non esattamente la stessa conduttura solito più (cmd2 | cmd1) - se i comandi sono intrinseche della shell (es while), la versione conduttura li esegue in subshells , e qualsiasi variabile essi fissati (ad esempio la a matrice) vengono persi quando uscita. cmd1 < <(cmd2) viene eseguito solo cmd2 in una subshell, quindi l'array vite passate la sua costruzione. Attenzione: questa forma di reindirizzamento è disponibile solo in bash, nemmeno bash in modalità sh emulazione; è necessario avviare lo script con #!/bin/bash.

Inoltre, poiché la fase di elaborazione dei file (in questo caso, solo a[i++]="$file", ma si potrebbe desiderare di fare qualcosa di più elaborato direttamente nel circuito) ha riorientato il suo ingresso, non può utilizzare tutti i comandi che potrebbero letti da stdin. Per evitare questa limitazione, tendo a usare:

unset a i
while IFS= read -r -u3 -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done 3< <(find /tmp -type f -print0)

... che passa l'elenco di file tramite l'unità 3, piuttosto che stdin.

Altri suggerimenti

Forse stai cercando xargs:

find . -print0 | xargs -r0 do_something_useful

L'opzione -L 1 potrebbe essere utile anche per voi, il che rende xargs do_something_useful exec con l'argomento solo 1 file.

Il problema principale è che il delimitatore NUL (\ 0) è inutile qui, perché non è possibile assegnare un IFS NUL-valore. Così come buoni programmatori ci prendiamo cura, che l'ingresso per il nostro programma è qualcosa che è in grado di gestire.

Per prima cosa creiamo un piccolo programma, che fa questa parte per noi:

#!/bin/bash
printf "%s" "$@" | base64

... e lo chiamano base64str (non dimenticate chmod + x)

In secondo luogo possiamo ora utilizzare un semplice e diretto per-ciclo:

for i in `find -type f -exec base64str '{}' \;`
do 
  file="`echo -n "$i" | base64 -d`"
  # do something with file
done

Quindi il trucco è, che un base64-stringa ha alcun segno che provoca guai per bash -., Naturalmente, un xxd o qualcosa di simile può anche fare il lavoro

Un altro modo di contare i file:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 

Da Bash 4.4, il mapfile incorporato ha l'interruttore -d (per specificare un delimitatore, simile al commutatore -d della dichiarazione read), e il delimitatore può essere il byte nullo. Quindi, una bella risposta alla domanda nel titolo

  

Acquisizione di uscita find . -print0 in un array bash

è:

mapfile -d '' ary < <(find . -print0)

Si può tranquillamente fare il conteggio con questo:

find . -exec echo ';' | wc -l

(stampa una nuova riga per ogni file / dir trovato, e poi contare le nuove righe stampate ...)

Credo che le soluzioni più eleganti esiste, ma io getterò questo in questo funzionerà anche per i nomi di file con spazi e / o ritorni a capo:.

i=0;
for f in *; do
  array[$i]="$f"
  ((i++))
done

È quindi possibile ad esempio elencare i file uno per uno (in questo caso in ordine inverso):

for ((i = $i - 1; i >= 0; i--)); do
  ls -al "${array[$i]}"
done

Questa pagina dà un bel esempio, e per di più vedi capitolo 26 nel Guida avanzata di scripting Bash .

Evitare xargs se potete:

man ruby | less -p 777 
IFS=$'\777' 
#array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) ) 
array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) ) 
echo ${#array[@]} 
printf "%s\n" "${array[@]}" | nl 
echo "${array[0]}" 
IFS=$' \t\n' 

Sono nuovo, ma credo che questa una risposta; Speranza che aiuta qualcuno:

STYLE="$HOME/.fluxbox/styles/"

declare -a array1

LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`


echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`

#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE

Questo è simile alla versione di Stephan202, ma i file (e directory) vengono messi in un array tutti in una volta. Il ciclo for qui è solo quello di "fare cose utili":

files=(*)                        # put files in current directory into an array
i=0
for file in "${files[@]}"
do
    echo "File ${i}: ${file}"    # do something useful 
    let i++
done

Per ottenere un conteggio:

echo ${#files[@]}

vecchia questione, ma nessuno ha suggerito questo metodo semplice, così ho pensato che sarebbe. Certo, se i nomi dei file hanno un ETX, questo non risolve il problema, ma ho il sospetto che serve per qualsiasi scenario del mondo reale. Cercando di utilizzare null sembra conciliarsi con movimentazione regole IFS predefinite. Stagione al vostro gusto con le opzioni di trovare e la gestione degli errori.

savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"

La risposta di Gordon Davisson è grande per bash. Tuttavia, un utile collegamento esiste per gli utenti zsh:

In primo luogo, mettere si stringa in una variabile:

A="$(find /tmp -type f -print0)"

Avanti, dividere questa variabile e memorizzarlo in un array:

B=( ${(s/^@/)A} )

C'è un trucco: ^@ è il carattere NUL. Per farlo, è necessario digitare Ctrl + V seguito da Ctrl + @.

È possibile controllare ogni voce di $ B contiene giusto valore:

for i in "$B[@]"; echo \"$i\"

lettori attenti potrebbero notare che chiamata a find comando può essere evitato nella maggior parte dei casi utilizzando la sintassi **. Ad esempio:

B=( /tmp/** )

Bash non è mai stato bravo a gestire i nomi dei file (o qualsiasi testo in realtà) perché utilizza gli spazi come delimitatore lista.

Mi consiglia di utilizzare pitone con la libreria sh .

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