¿Cómo analizo el resultado del comando find cuando los nombres de archivo tienen espacios en ellos?

12

Usando un bucle como

for i in `find . -name \*.txt` 

se romperá si algunos nombres de archivo tienen espacios en ellos.

¿Qué técnica puedo usar para evitar este problema?

Scott C Wilson
fuente
1
Tenga en cuenta que los archivos también pueden tener nuevas líneas en su nombre de archivo. Por eso hay find -print0y xargs -0.
Daniel Beck

Respuestas:

12

Idealmente, no lo hace de esa manera, porque analizar los nombres de archivo correctamente en un script de shell siempre es difícil (arreglarlo para espacios, aún tendrá problemas con otros caracteres incrustados, en particular, nueva línea). Esto incluso aparece como la primera entrada en la página BashPitfalls.

Dicho esto, hay una manera de hacer casi lo que quieres:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

Recuerde también citar $icuando lo use, para evitar otras cosas que interpreten los espacios más adelante. También recuerde $IFSretroceder después de usarlo, porque no hacerlo causará errores desconcertantes más tarde.

Esto tiene otra advertencia adjunta: lo que sucede dentro del whileciclo puede tener lugar en un subshell, dependiendo del shell exacto que esté utilizando, por lo que las configuraciones variables pueden no persistir. La forversión de bucle evita eso, pero al precio que, incluso si aplica la $IFSsolución para evitar problemas con los espacios, se meterá en problemas si finddevuelve demasiados archivos.

En algún momento, la solución correcta para todo esto se hace en un lenguaje como Perl o Python en lugar de shell.

geekosaur
fuente
1
Me gusta la idea de usar Python para evitar todo esto.
Scott C Wilson
12

Úselo find -print0y canalícelo xargs -0, o escriba su propio pequeño programa C y canalícelo a su pequeño programa C. Esto es lo que -print0y -0se inventó para.

Los scripts de shell no son la mejor manera de manejar nombres de archivos con espacios en ellos: puede hacerlo, pero se vuelve torpe.

DW
fuente
Funciona en mi máquina ^ TM!
mcandre 01 de
2

Puede establecer el "separador de campo interno" ( IFS) en algo más que espacio para la división del argumento de bucle, p. Ej.

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

Restablezco IFSdespués de su uso en find, principalmente porque se ve bien, creo. No he visto ningún problema al configurarlo en nueva línea, pero creo que esto es "más limpio".

Otro método, dependiendo de lo que desee hacer con la salida find, es usar directamente -execcon el findcomando o usarlo -print0y canalizarlo xargs -0. En el primer caso findse encarga del escape del nombre del archivo. En el -print0caso, findimprime su salida con un separador nulo y luego se xargsdivide en esto. Como ningún nombre de archivo puede contener ese carácter (lo que sé), esto también siempre es seguro. Esto es sobre todo útil en casos simples; y generalmente no es un gran sustituto para un forciclo completo .

Daniel Andersson
fuente
1

Usando find -print0conxargs -0

El uso find -print0combinado con xargs -0es completamente robusto contra los nombres de archivos legales, y es uno de los métodos más extensibles disponibles. Por ejemplo, supongamos que desea una lista de cada archivo PDF dentro del directorio actual. Podrías escribir

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

Esto encontrará cada PDF (vía -iname '*.pdf') en el directorio actual ( .) y cualquier subdirectorio, y pasará cada uno de ellos como argumento al echocomando. Como especificamos la -n 1opción, xargssolo pasaremos un argumento a la vez a echo. Si hubiéramos omitido esa opción, xargshabría pasado la mayor cantidad posible a echo. (Puede echo short input | xargs --show-limitsver cuántos bytes están permitidos en una línea de comando).

¿Qué hace xargsexactamente?

Podemos ver claramente el efecto que xargstiene en su entrada, y el efecto de -nen particular, mediante el uso de un script que hace eco de sus argumentos de una manera más precisa que echo.

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

Tenga en cuenta que maneja espacios y líneas nuevas perfectamente bien,

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

lo cual sería especialmente problemático con la siguiente solución común:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
Notas
jpaugh
fuente
1

No estoy de acuerdo con los bashbashers, porque bash, junto con el conjunto de herramientas * nix, es bastante experto en el manejo de archivos (incluidos aquellos cuyos nombres tienen espacios en blanco incrustados).

En realidad, findle da un control de grano fino sobre la elección de qué archivos procesar ... En el lado bash, realmente solo necesita darse cuenta de que debe convertir sus cadenas bash words; normalmente mediante el uso de "comillas dobles", o algún otro mecanismo como el uso de IFS, o los hallazgos{}

Tenga en cuenta que en la mayoría de las situaciones no es necesario configurar y restablecer IFS; simplemente use IFS localmente como se muestra en los ejemplos a continuación. Los tres manejan bien los espacios en blanco. Tampoco necesita una estructura de bucle "estándar", porque find's \; es efectivamente un bucle; simplemente ponga su lógica de bucle en una función bash (si no está llamando a una herramienta estándar).

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

Y dos ejemplos más

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'encontrar also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\; `)

Peter.O
fuente
1
Hay cierta validez en ambas perspectivas. Cuando solo estaba trabajando en mis propios archivos, solo usaba find y no me preocupaba, porque mis archivos no tienen espacios (o retornos de carro) en sus nombres. Pero cuando empiezas a trabajar con archivos de otras personas, tienes que usar técnicas más robustas.
Scott C Wilson el