¿Por qué mi script de shell se ahoga en espacios en blanco u otros caracteres especiales?

286

O, una guía introductoria para el manejo robusto de nombres de archivo y otras cadenas que pasan en scripts de shell.

Escribí un script de shell que funciona bien la mayor parte del tiempo. Pero se ahoga en algunas entradas (por ejemplo, en algunos nombres de archivo).

Encontré un problema como el siguiente:

  • Tengo un nombre de archivo que contiene un espacio hello world, y fue tratado como dos archivos separados helloy world.
  • Tengo una línea de entrada con dos espacios consecutivos y se redujeron a uno en la entrada.
  • Los espacios en blanco iniciales y finales desaparecen de las líneas de entrada.
  • A veces, cuando la entrada contiene uno de los caracteres \[*?, estos se reemplazan por algún texto que en realidad es el nombre de los archivos.
  • Hay un apóstrofe '(o una comilla doble ") en la entrada y las cosas se pusieron raras después de ese punto.
  • Hay una barra invertida en la entrada (o: estoy usando Cygwin y algunos de mis nombres de archivo tienen \separadores de estilo Windows ).

¿Qué está pasando y cómo lo soluciono?

Gilles
fuente
16
shellcheckayudarlo a mejorar la calidad de sus programas.
aurelien
3
Además de las técnicas de protección descritas en las respuestas, y aunque probablemente sea obvio para la mayoría de los lectores, creo que vale la pena comentar que cuando los archivos están destinados a ser procesados ​​utilizando herramientas de línea de comandos, es una buena práctica evitar caracteres sofisticados en el nombres en primer lugar, si es posible.
bli
1
@bli No, eso hace que solo los errores tarden más en aparecer. Está escondiendo errores hoy. Y ahora, no conoce todos los nombres de archivo que luego se usaron con su código.
Volker Siegel
En primer lugar, si sus parámetros contienen espacios, entonces deben ser citados entrando (en la línea de comando). Sin embargo, puede tomar toda la línea de comando y analizarla usted mismo. Dos espacios no se convierten en un espacio; cualquier cantidad de espacio le dice a su script que es la siguiente variable, por lo que si hace algo como "echo $ 1 $ 2" es su script colocando un espacio en el medio. También use "find (-exec)" para iterar sobre archivos con espacios en lugar de un bucle for; Puedes ocuparte de los espacios más fácilmente.
Patrick Taylor

Respuestas:

352

Siempre use comillas dobles sustituciones de variables y sustituciones de comando: "$foo","$(foo)"

Si usa sin $foocomillas, su secuencia de comandos se ahogará en la entrada o los parámetros (o la salida del comando, con $(foo)) que contienen espacios en blanco o \[*?.

Ahí puedes dejar de leer. Bueno, ok, aquí hay algunos más:

  • read- Para leer la línea de entrada de acuerdo con la readorden interna, utilizarwhile IFS= read -r line; do …
    Plain readbarras invertidas trata y espacios en blanco en especial.
  • xargs- Evitarxargs . Si debe usar xargs, haga eso xargs -0. En lugar de find … | xargs, prefierafind … -exec … .
    xargstrata los espacios en blanco y los caracteres \"'especialmente.

Esta respuesta se aplica a los depósitos / estilo POSIX (Bourne sh, ash, dash, bash, ksh, mksh, yash...). Los usuarios de Zsh deben omitirlo y leer el final de ¿ Cuándo es necesaria una doble cita? en lugar. Si desea todo lo esencial, lea el estándar o el manual de su shell.


Tenga en cuenta que las explicaciones a continuación contienen algunas aproximaciones (afirmaciones que son ciertas en la mayoría de las condiciones pero que pueden verse afectadas por el contexto o la configuración).

¿Por qué necesito escribir "$foo"? ¿Qué pasa sin las comillas?

$foono significa "tomar el valor de la variable foo". Significa algo mucho más complejo:

  • Primero, tome el valor de la variable.
  • División de campos: trate ese valor como una lista de campos separados por espacios en blanco y cree la lista resultante. Por ejemplo, si la variable contiene foo * bar ​entonces el resultado de este paso es la lista 3-elemento foo, *, bar.
  • Generación de nombre de archivo: trate cada campo como un globo, es decir, como un patrón comodín, y reemplácelo por la lista de nombres de archivo que coinciden con este patrón. Si el patrón no coincide con ningún archivo, no se modifica. En nuestro ejemplo, esto da como resultado la lista que contiene foo, seguida de la lista de archivos en el directorio actual, y finalmente bar. Si el directorio actual está vacía, el resultado es foo, *, bar.

Tenga en cuenta que el resultado es una lista de cadenas. Hay dos contextos en la sintaxis de shell: contexto de lista y contexto de cadena. La división de campos y la generación de nombre de archivo solo ocurren en el contexto de la lista, pero eso es la mayor parte del tiempo. Las comillas dobles delimitan un contexto de cadena: la cadena completa entre comillas dobles es una sola cadena, que no se debe dividir. (Excepción: "$@"expandirse a la lista de parámetros posicionales, por ejemplo, "$@"es equivalente a "$1" "$2" "$3"si hay tres parámetros posicionales. Consulte ¿Cuál es la diferencia entre $ * y $ @? )

Lo mismo sucede con la sustitución de comandos con $(foo)o con `foo`. En una nota al margen, no use `foo`: sus reglas de cotización son extrañas y no portátiles, y todos los shells modernos $(foo)son absolutamente equivalentes, excepto por tener reglas de cotización intuitivas.

La salida de la sustitución aritmética también sufre las mismas expansiones, pero eso normalmente no es una preocupación, ya que solo contiene caracteres no expandibles (suponiendo IFSque no contenga dígitos o -).

Ver ¿ Cuándo es necesaria la doble cita? para obtener más detalles sobre los casos en que puede omitir las citas.

A menos que quiera que ocurra todo este rigmarole, recuerde siempre usar comillas dobles alrededor de las sustituciones de variables y comandos. Tenga cuidado: omitir las comillas puede conducir no solo a errores sino a agujeros de seguridad .

¿Cómo proceso una lista de nombres de archivo?

Si escribe myfiles="file1 file2", con espacios para separar los archivos, esto no puede funcionar con nombres de archivos que contienen espacios. Los nombres de archivos Unix pueden contener cualquier carácter que no sea /(que siempre es un separador de directorio) y bytes nulos (que no puede usar en los scripts de shell con la mayoría de los shells).

Mismo problema con myfiles=*.txt; … process $myfiles. Cuando hace esto, la variable myfilescontiene la cadena de 5 caracteres *.txt, y es cuando escribe $myfilesque se expande el comodín. Este ejemplo realmente funcionará, hasta que cambie su script para que sea myfiles="$someprefix*.txt"; … process $myfiles. Si someprefixse establece en final report, esto no funcionará.

Para procesar una lista de cualquier tipo (como nombres de archivos), colóquela en una matriz. Esto requiere mksh, ksh93, yash o bash (o zsh, que no tiene todos estos problemas de citas); un shell POSIX simple (como ash o dash) no tiene variables de matriz.

myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"

Ksh88 tiene variables de matriz con una sintaxis de asignación diferente set -A myfiles "someprefix"*.txt(consulte la variable de asignación en un entorno ksh diferente si necesita portabilidad ksh88 / bash). Los shells de estilo Bourne / POSIX tienen una única matriz, la matriz de parámetros posicionales con los "$@"que establece sety que es local para una función:

set -- "$someprefix"*.txt
process -- "$@"

¿Qué pasa con los nombres de archivo que comienzan con -?

En una nota relacionada, tenga en cuenta que los nombres de los archivos pueden comenzar con un -(guión / menos), que la mayoría de los comandos interpretan como una opción. Si tiene un nombre de archivo que comienza con una parte variable, asegúrese de pasarlo --antes, como en el fragmento anterior. Esto le indica al comando que ha llegado al final de las opciones, por lo que cualquier cosa después de eso es un nombre de archivo, incluso si comienza con -.

Alternativamente, puede asegurarse de que los nombres de sus archivos comiencen con un carácter distinto de -. Los nombres de archivos absolutos comienzan con /, y puede agregarlos ./al comienzo de los nombres relativos. El siguiente fragmento convierte el contenido de la variable fen una forma "segura" de referirse al mismo archivo con el que se garantiza que no comenzará -.

case "$f" in -*) "f=./$f";; esac

En una nota final sobre este tema, tenga en cuenta que algunos comandos interpretan -como entrada estándar o salida estándar, incluso después --. Si necesita hacer referencia a un archivo real llamado -, o si está llamando a dicho programa y no desea que lea desde stdin o escriba en stdout, asegúrese de volver a escribir -como se indica arriba. Consulte ¿Cuál es la diferencia entre "du -sh *" y "du -sh ./*"? para mayor discusión.

¿Cómo almaceno un comando en una variable?

"Comando" puede significar tres cosas: un nombre de comando (el nombre como un ejecutable, con o sin ruta completa, o el nombre de una función, incorporado o alias), un nombre de comando con argumentos o un código de shell. En consecuencia, hay diferentes formas de almacenarlos en una variable.

Si tiene un nombre de comando, simplemente guárdelo y use la variable con comillas dobles como de costumbre.

command_path="$1"

"$command_path" --option --message="hello world"

Si tiene un comando con argumentos, el problema es el mismo que con una lista de nombres de archivo anteriores: esta es una lista de cadenas, no una cadena. No puede simplemente rellenar los argumentos en una sola cadena con espacios en el medio, porque si lo hace, no puede distinguir la diferencia entre los espacios que forman parte de los argumentos y los espacios que separan los argumentos. Si su shell tiene matrices, puede usarlas.

cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"

¿Qué pasa si está utilizando un shell sin matrices? Aún puede usar los parámetros posicionales, si no le importa modificarlos.

set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"

¿Qué sucede si necesita almacenar un comando de shell complejo, por ejemplo, con redirecciones, tuberías, etc.? ¿O si no desea modificar los parámetros posicionales? Luego puede construir una cadena que contenga el comando y usar el evalincorporado.

code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"

Tenga en cuenta las comillas anidadas en la definición de code: las comillas simples '…'delimitan un literal de cadena, de modo que el valor de la variable codees la cadena /path/to/executable --option --message="hello world" -- /path/to/file1. El evalbuiltin le dice al shell que analice la cadena pasada como argumento como si apareciera en el script, por lo que en ese punto se analizan las comillas y la tubería, etc.

Usar evales complicado. Piensa cuidadosamente sobre lo que se analiza cuando. En particular, no puede simplemente introducir un nombre de archivo en el código: debe citarlo, como lo haría si estuviera en un archivo de código fuente. No hay forma directa de hacer eso. Algo así como code="$code $filename"roturas si el nombre de archivo contiene ningún carácter especial shell (espacios, $, ;, |, <, >, etc.). code="$code \"$filename\""Todavía se rompe "$\`. Incluso se code="$code '$filename'"rompe si el nombre del archivo contiene un '. Hay dos soluciones

  • Agregue una capa de comillas alrededor del nombre del archivo. La forma más fácil de hacerlo es agregar comillas simples a su alrededor y reemplazar las comillas simples por '\''.

    quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
    code="$code '${quoted_filename%.}'"
  • Mantenga la expansión variable dentro del código, de modo que se busque cuando se evalúa el código, no cuando se construye el fragmento de código. Esto es más simple pero solo funciona si la variable todavía está alrededor con el mismo valor en el momento en que se ejecuta el código, no por ejemplo, si el código se construye en un bucle.

    code="$code \"\$filename\""

Finalmente, ¿realmente necesitas una variable que contenga código? La forma más natural de dar un nombre a un bloque de código es definir una función.

¿Qué pasa con read?

Sin -r, readpermite líneas de continuación: esta es una sola línea lógica de entrada:

hello \
world

readdivide la línea de entrada en campos delimitados por caracteres en $IFS(sin -r, la barra diagonal inversa también escapa a esos). Por ejemplo, si la entrada es una línea que contiene tres palabras, se read first second thirdestablece firsten la primera palabra de entrada, seconden la segunda palabra y thirden la tercera palabra. Si hay más palabras, la última variable contiene todo lo que queda después de configurar las anteriores. Los espacios en blanco iniciales y finales se recortan.

Establecer IFSla cadena vacía evita cualquier recorte. Vea Por qué se usa `while IFS = read` con tanta frecuencia, en lugar de` IFS =; mientras lee..`? Para una explicación más larga.

¿Qué tiene de malo xargs?

El formato de entrada de xargses cadenas separadas por espacios en blanco que opcionalmente pueden ser entre comillas simples o dobles. Ninguna herramienta estándar genera este formato.

La entrada xargs -L1ao xargs -les casi una lista de líneas, pero no del todo: si hay un espacio al final de una línea, la siguiente línea es una línea de continuación.

Puede usar xargs -0donde corresponda (y donde esté disponible: GNU (Linux, Cygwin), BusyBox, BSD, OSX, pero no está en POSIX). Eso es seguro, porque los bytes nulos no pueden aparecer en la mayoría de los datos, en particular en los nombres de archivo. Para producir una lista de nombres de archivo separados por nulos, use find … -print0(o puede usar find … -exec …como se explica a continuación).

¿Cómo proceso los archivos encontrados por find?

find  -exec some_command a_parameter another_parameter {} +

some_commanddebe ser un comando externo, no puede ser una función de shell o un alias. Si necesita invocar un shell para procesar los archivos, llame shexplícitamente.

find  -exec sh -c '
  for x do
    … # process the file "$x"
  done
' find-sh {} +

Tengo otra pregunta

Explore la etiqueta de en este sitio, o o . (Haga clic en "aprender más ..." para ver algunos consejos generales y una lista seleccionada a mano de preguntas comunes). Si ha buscado y no puede encontrar una respuesta, pregunte .

Gilles
fuente
66
@ John1024 Es solo una característica de GNU, así que me quedaré con "ninguna herramienta estándar".
Gilles
2
También necesita comillas $(( ... ))(también $[...]en algunos shells) excepto en zsh(incluso en emulación sh) y mksh.
Stéphane Chazelas
3
Tenga en cuenta que xargs -0no es POSIX. Excepto con FreeBSD xargs, generalmente desea en xargs -r0lugar de xargs -0.
Stéphane Chazelas
2
@ John1024, no, ls --quoting-style=shell-alwaysno es compatible con xargs. Pruebatouch $'a\nb'; ls --quoting-style=shell-always | xargs
Stéphane Chazelas
3
Otra buena característica (solo GNU) es xargs -d "\n"para que pueda ejecutar, por ejemplo, locate PATTERN1 |xargs -d "\n" grep PATTERN2para buscar nombres de archivo que coincidan con PATTERN1 con contenido que coincida con PATTERN2 . Sin GNU, puede hacerlo, por ejemplo, comolocate PATTERN1 |perl -pne 's/\n/\0/' |xargs -0 grep PATTERN1
Adam Katz
26

Si bien la respuesta de Gilles es excelente, yo cuestiono su punto principal

Utilice siempre comillas dobles alrededor de sustituciones variables y sustituciones de comandos: "$ foo", "$ (foo)"

Cuando está comenzando con un shell tipo Bash que divide las palabras, sí, por supuesto, el consejo seguro es siempre usar comillas. Sin embargo, la división de palabras no siempre se realiza

§ División de palabras

Estos comandos pueden ejecutarse sin error

foo=$bar
bar=$(a command)
logfile=$logdir/foo-$(date +%Y%m%d)
PATH=/usr/local/bin:$PATH ./myscript
case $foo in bar) echo bar ;; baz) echo baz ;; esac

No estoy alentando a los usuarios a adoptar este comportamiento, pero si alguien comprende con firmeza cuándo se produce la división de palabras, entonces deberían poder decidir por sí mismos cuándo usar comillas.

Steven Penny
fuente
19
Como menciono en mi respuesta, vea unix.stackexchange.com/questions/68694/… para más detalles. Observe la pregunta: "¿Por qué se ahoga mi script de shell?". El problema más común (por años de experiencia en este sitio y en otros lugares) es la falta de comillas dobles. "Usar siempre comillas dobles" es más fácil de recordar que "usar siempre comillas dobles, excepto en los casos en que no son necesarios".
Gilles
14
Las reglas son difíciles de entender para los principiantes. Por ejemplo, foo=$barestá bien, pero export foo=$baro env foo=$varno lo está (al menos en algunos shells). Un consejo para principiantes: siempre cite sus variables a menos que sepa lo que está haciendo y tenga una buena razón para no hacerlo .
Stéphane Chazelas
55
@StevenPenny ¿Es realmente más correcto? ¿Hay casos razonables donde las citas romperían el guión? En situaciones donde en la mitad de los casos se deben usar comillas , y en otras medias comillas se pueden usar opcionalmente, entonces una recomendación "siempre use comillas, por si acaso" es la que debe pensarse, ya que es cierto, simple y menos riesgoso. Se sabe que enseñar tales listas de excepciones a principiantes es ineficaz (carece de contexto, no los recordarán) y es contraproducente, ya que confundirán las citas necesarias / innecesarias, romperán sus guiones y los desmotivarán para aprender más.
Peteris
66
Mis $ 0.02 serían que recomendar cotizar todo es un buen consejo. Citar erróneamente algo que no lo necesita es inofensivo, y no citar erróneamente algo que sí lo necesita es perjudicial. Por lo tanto, para la mayoría de los autores de scripts de shell que nunca entenderán las complejidades de cuándo ocurre exactamente la división de palabras, citar todo es mucho más seguro que tratar de citar solo cuando sea necesario.
godlygeek
55
@Peteris y godlygeek: "¿Hay casos razonables donde las citas romperían el guión?" Depende de su definición de "razonable". Si se establece un script criteria="-type f", entonces find . $criteriafunciona pero find . "$criteria"no funciona.
G-Man
22

Hasta donde yo sé, solo hay dos casos en los que es necesario hacer comillas dobles de expansiones, y esos casos involucran los dos parámetros especiales de shell "$@"y "$*", que se especifican para expandirse de manera diferente cuando están encerrados entre comillas dobles. En todos los demás casos (excluyendo, quizás, implementaciones de matriz específicas de shell), el comportamiento de una expansión es algo configurable: hay opciones para eso.

Esto no quiere decir, por supuesto, que se deben evitar las comillas dobles; por el contrario, es probablemente el método más conveniente y robusto para delimitar una expansión que el shell tiene para ofrecer. Pero creo que, como las alternativas ya han sido expuestas por expertos, este es un excelente lugar para discutir lo que sucede cuando el shell expande un valor.

El caparazón, en su corazón y alma (para aquellos que lo tienen) , es un intérprete de comandos, es un analizador, como un gran, interactivo sed. Si su declaración de shell se ahoga en espacios en blanco o similar, es muy probable porque no ha entendido completamente el proceso de interpretación del shell, especialmente cómo y por qué traduce una declaración de entrada en un comando procesable. El trabajo del shell es:

  1. aceptar entrada

  2. interpretarlo y dividirlo correctamente en palabras de entrada con token

    • las palabras de entrada son los elementos de sintaxis de shell como $wordoecho $words 3 4* 5

    • las palabras siempre se dividen en espacios en blanco, eso es solo sintaxis, pero solo los caracteres de espacios en blanco literales se sirven al shell en su archivo de entrada

  3. expandirlos si es necesario en múltiples campos

    • los campos resultan de expansiones de palabras : constituyen el comando ejecutable final

    • exceptuando "$@", $IFS campo de división , y la expansión ruta una entrada palabra debe evaluar siempre a un solo campo .

  4. y luego ejecutar el comando resultante

    • en la mayoría de los casos, esto implica transmitir los resultados de su interpretación de una forma u otra

La gente a menudo dice que el caparazón es un pegamento y, si esto es cierto, entonces lo que está pegando son listas de argumentos, o campos , de un proceso u otro cuando se trata de execellos. La mayoría de los shells no manejan bien el NULbyte, si es que lo hacen, y esto se debe a que ya se están dividiendo. El shell tiene exec mucho que hacer y debe hacerlo con una NULmatriz delimitada de argumentos que entrega al núcleo del sistema en ese execmomento. Si tuviera que mezclar el delimitador del shell con sus datos delimitados, entonces el shell probablemente lo arruinaría. Sus estructuras de datos internos, como la mayoría de los programas, se basan en ese delimitador. zsh, notablemente, no arruina esto.

Y ahí es donde $IFSentra. $IFSEs un parámetro de shell siempre presente, y también configurable, que define cómo el shell debe dividir las expansiones de shell de palabra a campo , específicamente en qué valores deben delimitar esos campos . $IFSdivide las expansiones de shell en delimitadores que no sean NUL, o, en otras palabras, el shell sustituye bytes que resultan de una expansión que coincide con aquellos en el valor de $IFSwith NULen sus matrices de datos internas. Cuando lo mira así, puede comenzar a ver que cada expansión de shell dividida en campo es una $IFSmatriz de datos delimitada.

Es importante comprender que $IFSsolo delimita las expansiones que no están delimitadas de otra manera, lo que puede hacer con "comillas dobles. Cuando cita una expansión, la delimita en la cabeza y al menos en la cola de su valor. En esos casos $IFSno se aplica ya que no hay campos para separar. De hecho, una expansión con comillas dobles exhibe un comportamiento de división de campo idéntico a una expansión sin comillas cuando IFS=se establece en un valor vacío.

A menos que se cite, $IFSes una $IFSexpansión de shell delimitada. El valor predeterminado es un valor específico de <space><tab><newline>- los tres exhiben propiedades especiales cuando están contenidos dentro $IFS. Mientras que $IFSse especifica cualquier otro valor para evaluar a un solo campo por cada ocurrencia de expansión , el $IFS espacio en blanco , cualquiera de esos tres, se especifica para eludir a un solo campo por secuencia de expansión y las secuencias iniciales / finales se eluyen por completo. Esto es probablemente más fácil de entender a través del ejemplo.

slashes=///// spaces='     '
IFS=/; printf '<%s>' $slashes$spaces
<><><><><><     >
IFS=' '; printf '<%s>' $slashes$spaces
</////>
IFS=; printf '<%s>' $slashes$spaces
</////     >
unset IFS; printf '<%s>' "$slashes$spaces"
</////     >

Pero eso es solo $IFS: solo la división de palabras o el espacio en blanco como se le preguntó, entonces, ¿qué pasa con los caracteres especiales ?

El shell, de forma predeterminada, también expandirá ciertos tokens sin comillas ( ?*[como se indica en otro lugar aquí) en múltiples campos cuando aparecen en una lista. Esto se llama expansión de nombre de ruta o globbing . Es una herramienta increíblemente útil y, como ocurre después de la división de campos en el orden de análisis del shell, no se ve afectada por $ IFS : los campos generados por la expansión de un nombre de ruta se delimitan en la cabecera / cola de los nombres de archivo, independientemente de si sus contenidos contienen caracteres actualmente en $IFS. Este comportamiento está activado de forma predeterminada, pero de lo contrario se configura muy fácilmente.

set -f

Que indica al shell no a glob . La expansión del nombre de ruta no ocurrirá al menos hasta que esa configuración se deshaga de alguna manera, como si el shell actual se reemplaza por otro nuevo proceso de shell o ...

set +f

... se emite a la cáscara. Las comillas dobles, como también lo hacen para $IFS la división de campos , hacen que esta configuración global sea innecesaria por expansión. Entonces:

echo "*" *

... si la expansión de nombre de ruta está habilitada actualmente, probablemente producirá resultados muy diferentes por argumento, ya que el primero se expandirá solo a su valor literal (el carácter de asterisco único, es decir, en absoluto) y el segundo solo al mismo si el directorio de trabajo actual no contiene nombres de archivo que puedan coincidir (y coincide con casi todos) . Sin embargo, si lo haces:

set -f; echo "*" *

... los resultados para ambos argumentos son idénticos; *en ese caso, no se expande.

mikeserv
fuente
De hecho, estoy de acuerdo con @ StéphaneChazelas de que (en su mayoría) confunde las cosas más que ayudar ... pero personalmente me pareció útil, así que voté positivamente. Ahora tengo una mejor idea (y algunos ejemplos) de cómo IFSfunciona realmente. Lo que no entiendo es por qué sería jamás ser una buena idea establecer IFSa algo que no sea por defecto.
Comodín el
1
@Wildcard: es un delimitador de campo. si tiene un valor en una variable que desea expandir a múltiples campos, divídalo $IFS. cd /usr/bin; set -f; IFS=/; for path_component in $PWD; do echo $path_component; doneimpresiones \nentonces usr\nentonces bin\n. El primero echoestá vacío porque /es un campo nulo. Los componentes_ruta pueden tener nuevas líneas o espacios o lo que sea, no importaría porque los componentes estaban divididos /y no el valor predeterminado. la gente lo hace awktodo el tiempo, de todos modos. tu caparazón lo hace también
mikeserv
3

Tuve un gran proyecto de video con espacios en los nombres de archivo y espacios en los nombres de directorio. Si bien find -type f -print0 | xargs -0funciona para varios propósitos y en diferentes shells, encuentro que usar un IFS (separador de campo de entrada) personalizado le brinda más flexibilidad si está usando bash. El fragmento a continuación usa bash y establece IFS en solo una nueva línea; siempre que no haya nuevas líneas en sus nombres de archivo:

(IFS=$'\n'; for i in $(find -type f -print) ; do
    echo ">>>$i<<<"
done)

Tenga en cuenta el uso de parens para aislar la redefinición de IFS. He leído otras publicaciones sobre cómo recuperar IFS, pero esto es más fácil.

Además, configurar IFS en nueva línea le permite establecer variables de shell de antemano e imprimirlas fácilmente. Por ejemplo, puedo hacer crecer una variable V incrementalmente usando nuevas líneas como separadores:

V=""
V="./Ralphie's Camcorder/STREAM/00123.MTS,04:58,05:52,-vf yadif"
V="$V"$'\n'"./Ralphie's Camcorder/STREAM/00111.MTS,00:00,59:59,-vf yadif"
V="$V"$'\n'"next item goes here..."

y correspondientemente:

(IFS=$'\n'; for v in $V ; do
    echo ">>>$v<<<"
done)

Ahora puedo "enumerar" la configuración de V echo "$V"usando comillas dobles para generar las nuevas líneas. (Agradezca a este hilo por la $'\n'explicación).

Russ
fuente
3
Pero aún tendrá problemas con los nombres de archivo que contienen caracteres de nueva línea o globales. Ver también: ¿Por qué es un bucle sobre la salida de find una mala práctica? . Si se usa zsh, puede usar IFS=$'\0'y usar -print0( zshno hace glob en las expansiones, por lo que los caracteres glob no son un problema allí).
Stéphane Chazelas
1
Esto funciona con nombres de archivo que contienen espacios, pero no funciona con nombres de archivo potencialmente hostiles o nombres de archivo "sin sentido" accidentales. Puede solucionar fácilmente el problema de los nombres de archivo que contienen caracteres comodín agregando set -f. Por otro lado, su enfoque falla fundamentalmente con los nombres de archivo que contienen nuevas líneas. Cuando se trata de datos que no sean nombres de archivo, también falla con elementos vacíos.
Gilles
Bien, mi advertencia es que no funcionará con nuevas líneas en los nombres de archivo. Sin embargo, creo que tenemos que trazar la línea justo antes de la locura ;-)
Russ
Y no estoy seguro de por qué esto recibió un voto negativo. Este es un método perfectamente razonable para iterar sobre nombres de archivos con espacios. Usar -print0 requiere xargs, y hay cosas que son difíciles de usar con esa cadena. Lamento que alguien no esté de acuerdo con mi respuesta, pero esa no es razón para rechazarla.
Russ
0

Teniendo en cuenta todas las implicaciones de seguridad mencionadas anteriormente y suponiendo que confía y tiene control sobre las variables que está expandiendo, es posible tener múltiples rutas con espacios en blanco eval. ¡Pero ten cuidado!

$ FILES='"a b" c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
$ FILES='a\ b c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
Mattias Wadman
fuente