Siempre use comillas dobles sustituciones de variables y sustituciones de comando: "$foo"
,"$(foo)"
Si usa sin $foo
comillas, 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 read
orden interna, utilizarwhile IFS= read -r line; do …
Plain read
barras 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 …
.
xargs
trata 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?
$foo
no 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 IFS
que 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 myfiles
contiene la cadena de 5 caracteres *.txt
, y es cuando escribe $myfiles
que se expande el comodín. Este ejemplo realmente funcionará, hasta que cambie su script para que sea myfiles="$someprefix*.txt"; … process $myfiles
. Si someprefix
se 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 set
y 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 f
en 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 eval
incorporado.
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 code
es la cadena /path/to/executable --option --message="hello world" -- /path/to/file1
. El eval
builtin 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 eval
es 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
, read
permite líneas de continuación: esta es una sola línea lógica de entrada:
hello \
world
read
divide 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 third
establece first
en la primera palabra de entrada, second
en la segunda palabra y third
en 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 IFS
la 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 xargs
es 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 -L1
ao xargs -l
es 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 -0
donde 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_command
debe 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 sh
explícitamente.
find … -exec sh -c '
for x do
… # process the file "$x"
done
' find-sh {} +
Tengo otra pregunta
Explore la etiqueta de comillas en este sitio, o shell o shell-script . (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 .
shellcheck
ayudarlo a mejorar la calidad de sus programas.