El uso find . -print0
parece ser la única forma segura de obtener una lista de archivos en bash debido a la posibilidad de que los nombres de archivo contengan espacios, nuevas líneas, comillas, etc.
Sin embargo, estoy teniendo dificultades para hacer que la salida de find sea útil dentro de bash o con otras utilidades de línea de comando. La única forma en que he logrado hacer uso de la salida es canalizándola a perl y cambiando el IFS de perl a nulo:
find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'
Este ejemplo imprime la cantidad de archivos encontrados, evitando el peligro de que las nuevas líneas en los nombres de archivo corrompan el recuento, como ocurriría con:
find . | wc -l
Como la mayoría de los programas de línea de comando no admiten la entrada delimitada por nulos, me imagino que lo mejor sería capturar la salida de find . -print0
en una matriz bash, como he hecho en el fragmento de Perl anterior, y luego continuar con la tarea, sea lo que sea. ser.
¿Cómo puedo hacer esto?
Esto no funciona:
find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )
Una pregunta mucho más general podría ser: ¿Cómo puedo hacer cosas útiles con listas de archivos en bash?
Respuestas:
Robado descaradamente 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 la construcción de redirección que se usa aquí (
cmd1 < <(cmd2)
) es similar, pero no igual que la tubería más habitual (cmd2 | cmd1
): si los comandos son elementos integrados de shell (pwhile
. Ej. ), La versión de la tubería los ejecuta en subcapas y cualquier variable que establezcan. (por ejemplo, la matriza
) se pierden cuando salen.cmd1 < <(cmd2)
solo ejecuta cmd2 en una subcapa, por lo que la matriz vive más allá de su construcción. Advertencia: esta forma de redirección solo está disponible en bash, ni siquiera bash en el modo de emulación sh; debe comenzar su guión con#!/bin/bash
.Además, debido a que el paso de procesamiento del archivo (en este caso, simplemente
a[i++]="$file"
, pero es posible que desee hacer algo más elegante directamente en el ciclo) tiene su entrada redirigida, no puede usar ningún comando que pueda leer desde stdin. Para evitar esta limitación, tiendo a usar: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 la lista de archivos a través de la unidad 3, en lugar de stdin.
fuente
\177
) en cadenas. IIRC, incluso x = "$ y" no siempre funcionaría bien con estos caracteres. Acabo de probar con bash 2.05b.0 y 3.2.17 (el más antiguo y el más nuevo que tengo a mano); ambos manejaron las nuevas líneas correctamente, pero v2.05b.0 se comió el carácter de eliminación.-d ''
es equivalente a-d $'\0'
.arr+=("$file")
Quizás estés buscando xargs:
La opción -L 1 también podría serle útil, lo que hace que xargs exec haga_something_useful con solo 1 argumento de archivo.
fuente
Desde Bash 4.4, la función incorporada
mapfile
tiene el-d
modificador (para especificar un delimitador, similar al-d
cambio de laread
instrucción), y el delimitador puede ser el byte nulo. Por lo tanto, una buena respuesta a la pregunta del título.es:
mapfile -d '' ary < <(find . -print0)
fuente
El principal problema es que el delimitador NUL (\ 0) es inútil aquí, porque no es posible asignar a IFS un valor NUL. Entonces, como buenos programadores, nos ocupamos de que la entrada de nuestro programa sea algo que pueda manejar.
Primero creamos un pequeño programa, que hace esta parte por nosotros:
#!/bin/bash printf "%s" "$@" | base64
... y llámalo base64str (no olvides chmod + x)
En segundo lugar, ahora podemos usar un bucle for simple y directo:
for i in `find -type f -exec base64str '{}' \;` do file="`echo -n "$i" | base64 -d`" # do something with file done
Entonces, el truco es que una cadena base64 no tiene ningún signo que cause problemas para bash; por supuesto, un xxd o algo similar también puede hacer el trabajo.
fuente
read -r -d ''
leerá todo hasta el próximo NUL en"$REPLY"
. No hay necesidad de preocuparseIFS
.Otra forma más de contar archivos:
find /DIR -type f -print0 | tr -dc '\0' | wc -c
fuente
Puede hacer el recuento con seguridad con esto:
find . -exec echo ';' | wc -l
(Imprime una nueva línea para cada archivo / directorio encontrado, y luego cuenta las nuevas líneas impresas ...)
fuente
-printf
opción en lugar de-exec
para cada archivo:find . -printf "\n" | wc -l
Creo que existen soluciones más elegantes, pero agregaré esta. Esto también funcionará para nombres de archivo con espacios y / o líneas nuevas:
i=0; for f in *; do array[$i]="$f" ((i++)) done
A continuación, puede, por ejemplo, enumerar los archivos uno por uno (en este caso en orden inverso):
for ((i = $i - 1; i >= 0; i--)); do ls -al "${array[$i]}" done
Esta página ofrece un buen ejemplo y, para obtener más información, consulte el Capítulo 26 de la Guía avanzada de secuencias de comandos Bash .
fuente
Evite los xargs si puede:
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'
fuente
\777
?Soy nuevo pero creo que esta es una 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
fuente
Esto es similar a la versión de Stephan202, pero los archivos (y directorios) se colocan en una matriz todos a la vez. El
for
ciclo aquí es solo 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 un recuento:
echo ${#files[@]}
fuente
Pregunta vieja, pero nadie sugirió este método simple, así que pensé que lo haría. Por supuesto, si sus nombres de archivo tienen un ETX, esto no resuelve su problema, pero sospecho que sirve para cualquier escenario del mundo real. Intentar usar null parece contradecir las reglas de manejo de IFS predeterminadas. Sazone a su gusto con opciones de búsqueda y manejo de errores.
savedFS="$IFS" IFS=$'\x3' filenames=(`find wherever -printf %p$'\x3'`) IFS="$savedFS"
fuente
La respuesta de Gordon Davisson es genial para bash. Sin embargo, existe un atajo útil para los usuarios de zsh:
Primero, coloque su cadena en una variable:
A="$(find /tmp -type f -print0)"
A continuación, divida esta variable y guárdela en una matriz:
B=( ${(s/^@/)A} )
Hay un truco:
^@
es el carácter NUL. Para hacerlo, debe escribir Ctrl + V seguido de Ctrl + @.Puede verificar que cada entrada de $ B contenga el valor correcto:
for i in "$B[@]"; echo \"$i\"
Los lectores cuidadosos pueden notar que la llamada al
find
comando puede evitarse en la mayoría de los casos usando**
sintaxis. Por ejemplo:fuente
Bash nunca ha sido bueno manejando nombres de archivos (o cualquier texto en realidad) porque usa espacios como delimitadores de listas.
Recomiendo usar Python con la biblioteca sh en su lugar.
fuente