Pregunta

El uso de find . -print0 parece ser la única forma segura de obtener una lista de los archivos de fiesta debido a la posibilidad de que los nombres de archivo que contienen espacios, saltos de línea, comillas, etc.

Sin embargo, estoy teniendo dificultades para hacer realidad la salida del hallazgo útil dentro de bash o con otras utilidades de línea de comandos. La única manera que he logrado hacer uso de la salida está canalizando a Perl, y el cambio de IFS de Perl para nulo:

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

En este ejemplo se imprime el número de archivos encontrados, evitando el peligro de saltos de línea en los nombres de archivo que corrompen el conteo, como ocurriría con:

find . | wc -l

A medida que los programas de línea de la mayoría de comandos no son compatibles con entrada nula delimitado, supongo que lo mejor sería la de capturar la salida de find . -print0 en una matriz de fiesta, como lo han hecho en el Perl anteriores fragmentos, y luego continuar con la tarea , cualquiera que ésta sea.

¿Cómo puedo hacer esto?

Esto no funciona:

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

Una cuestión mucho más general podría ser: ¿Cómo puedo hacer cosas útiles con listas de archivos en bash

?
¿Fue útil?

Solución

robado Desvergonzadamente de BashFAQ de Greg :

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)

Tenga en cuenta que el constructo de redirección usada aquí (cmd1 < <(cmd2)) es similar a, pero no exactamente el mismo que el de la tubería habitual más (cmd2 | cmd1) - si los comandos son órdenes internas (por ejemplo while), la versión tubería los ejecuta en subcapas , y cualquier variable que se fijan (por ejemplo, la a array) se pierden cuando salen. cmd1 < <(cmd2) sólo se ejecuta cmd2 en un subnivel, por lo que la matriz vive más allá de su construcción. Advertencia: esta forma de redirección sólo está disponible en bash, ni siquiera en el modo fiesta sh-emulación; debe iniciar el guión con #!/bin/bash.

Además, debido a la etapa de procesamiento de archivos (en este caso, simplemente a[i++]="$file", pero es posible que desee hacer algo más elaborado directamente en el bucle) ha reorientado su entrada, no puede utilizar cualquier comando que pueda leer de la entrada estándar. Para evitar esta limitación, que tienden a utilizar:

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)

... que pasa a la lista de archivos a través de la unidad 3, en lugar de la entrada estándar.

Otros consejos

Quizás esté buscando xargs:

find . -print0 | xargs -r0 do_something_useful

La opción -L 1 podría ser útil para usted también, lo que hace xargs do_something_useful ejecutivo con el argumento sólo 1 archivo.

El problema principal es que el delimitador NUL (\ 0) es inútil aquí, porque no es posible asignar un NUL IFS-valor. Así como los buenos programadores nos ocupamos, que la entrada de nuestro programa es algo que es capaz de manejar.

Primero creamos un pequeño programa, lo que hace esta parte para nosotros:

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

... y lo llaman base64str (no se olvide chmod + x)

En segundo lugar, ahora podemos utilizar un simple y sencillo para bucle:

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

Así que el truco es, que una base 64-cadena tiene ninguna señal que causa problemas a golpe -. Por supuesto, un xxd o algo similar también puede hacer el trabajo

Sin embargo, otra manera de contar los archivos:

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

Desde Bash 4.4, la orden interna mapfile tiene el interruptor -d (para especificar un delimitador, similar al interruptor -d de la declaración read), y el delimitador puede ser el byte nulo. Por lo tanto, una buena respuesta a la pregunta del título

  

Captura de la salida de find . -print0 en una matriz de golpe

es:

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

Se puede hacer con seguridad el recuento con esto:

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

(Se imprime una nueva línea para cada archivo / dir encontrado, y luego contar las nuevas líneas impresas ...)

Creo que existen soluciones más elegantes, pero voy a tirar éste en Esto también funciona para los nombres de archivo con espacios y / o saltos de línea:.

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

A continuación, puede, por ejemplo, listar los archivos de uno en uno (en este caso en el orden inverso):

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

Esta página da un buen ejemplo, y para obtener más ver Capítulo 26 en el Guía avanzada Bash-Scripting .

Evitar xargs si es posible:

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' 

Soy nuevo, pero creo que esta respuesta; espero que ayude a alguien:

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

Esto es similar a la versión de Stephan202, pero los archivos (y directorios) se colocan en una matriz de una sola vez. El bucle for aquí es sólo para "hacer cosas útiles":

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

Para obtener una cuenta:

echo ${#files[@]}

vieja pregunta, pero nadie sugirió este método simple, así que pensé que lo haría. Por supuesto, si los nombres de archivo tienen un ETX, esto no resuelve su problema, pero sospecho que sirve para cualquier escenario del mundo real. Tratar de utilizar nula parece ir en contra de IFS por defecto de manipulación reglas. Temporada a su gusto con opciones de encontrar y tratamiento de errores.

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

La respuesta de Gordon Davisson es ideal para la fiesta. Sin embargo existe un atajo útil para los usuarios de zsh:

En primer lugar, coloque usted cadena en una variable:

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

A continuación, divide esta variable y la almacena en una matriz:

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

Hay un truco: ^@ es el carácter NUL. Para hacerlo, hay que pulsar Ctrl + V seguido por Ctrl + @.

Puede comprobar cada entrada de $ B contiene el valor correcto:

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

Los lectores atentos pueden notar que la llamada a find comando puede ser evitado en la mayoría de los casos utilizando la sintaxis **. Por ejemplo:

B=( /tmp/** )

Bash nunca ha sido bueno en el manejo de los nombres de archivo (o cualquier texto en realidad), ya que utiliza espacios como delimitador lista.

Me gustaría recomendar el uso de pitón con la biblioteca sh lugar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top