bash - Lectura de argumentos entre comillas/escapes correctamente desde una cadena
shell (4)
Me encuentro con un problema al pasar un argumento a un comando en un script Bash.
poc.sh:
#!/bin/bash
ARGS=''"hi there" test''
./swap ${ARGS}
intercambiar:
#!/bin/sh
echo "${2}" "${1}"
La salida actual es:
there" "hi
Cambiando solo poc.sh (ya que creo que swap hace lo que quiero correctamente), ¿cómo hago para que poc.sh pase "hola allí" y pruebe como dos argumentos, con "hola allí" sin comillas?
Alerta de idea fea: función Pure Bash
Aquí hay un analizador de cadenas entre comillas escrito en puro bash (¡qué terrible diversión!)
Advertencia : al igual que en el ejemplo de xargs anterior, este error se produce en el caso de una cita escapada. Esto podría solucionarse ... pero es mucho mejor hacerlo en un lenguaje de programación real.
Ejemplo de uso
MY_ARGS="foo ''bar baz'' qux * "''$(dangerous)''" sudo ls -lah"
# Create array from multi-line string
IFS=$''/r/n'' GLOBIGNORE=''*'' args=($(parseargs "$MY_ARGS"))
# Show each of the arguments array
for arg in "${args[@]}"; do
echo "$arg"
done
Salida de ejemplo
foo
bar baz
qux
*
Función de argumento de análisis
Esto literalmente va carácter por carácter y se agrega a la cadena actual o al conjunto actual.
set -u
set -e
# ParseArgs will parse a string that contains quoted strings the same as bash does
# (same as most other *nix shells do). This is secure in the sense that it doesn''t do any
# executing or interpreting. However, it also doesn''t do any escaping, so you shouldn''t pass
# these strings to shells without escaping them.
parseargs() {
notquote="-"
str=$1
declare -a args=()
s=""
# Strip leading space, then trailing space, then end with space.
str="${str## }"
str="${str%% }"
str+=" "
last_quote="${notquote}"
is_space=""
n=$(( ${#str} - 1 ))
for ((i=0;i<=$n;i+=1)); do
c="${str:$i:1}"
# If we''re ending a quote, break out and skip this character
if [ "$c" == "$last_quote" ]; then
last_quote=$notquote
continue
fi
# If we''re in a quote, count this character
if [ "$last_quote" != "$notquote" ]; then
s+=$c
continue
fi
# If we encounter a quote, enter it and skip this character
if [ "$c" == "''" ] || [ "$c" == ''"'' ]; then
is_space=""
last_quote=$c
continue
fi
# If it''s a space, store the string
re="[[:space:]]+" # must be used as a var, not a literal
if [[ $c =~ $re ]]; then
if [ "0" == "$i" ] || [ -n "$is_space" ]; then
echo continue $i $is_space
continue
fi
is_space="true"
args+=("$s")
s=""
continue
fi
is_space=""
s+="$c"
done
if [ "$last_quote" != "$notquote" ]; then
>&2 echo "error: quote not terminated"
return 1
fi
for arg in "${args[@]}"; do
echo "$arg"
done
return 0
}
Puedo o no mantener esto actualizado en:
Parece una cosa bastante estúpida que hacer ... pero tenía picazón ... oh, bueno.
Algunas palabras introductorias
Si es posible, no use cadenas entre comillas como formato de entrada.
-
Es difícil analizar de manera consistente: diferentes shells tienen diferentes extensiones, y diferentes implementaciones que no son de shell implementan diferentes subconjuntos (vea los deltas entre
shlex
yshlex
continuación). -
Es difícil de generar mediante programación.
ksh y bash tienen
printf ''%q''
, que generará una cadena entre comillas con contenido de una variable arbitraria, pero no existe un equivalente en el estándar POSIX sh. -
Es fácil analizar
mal
.
Muchas personas que consumen este formato usan
eval
, que tiene importantes preocupaciones de seguridad.
Las secuencias delimitadas por NUL son una práctica mucho mejor, ya que pueden representar con precisión cualquier posible matriz de shell o lista de argumentos sin ambigüedad alguna.
xargs, con bashismos
Si obtiene su lista de argumentos de una fuente de entrada generada por humanos mediante el uso de comillas de shell, puede considerar usar
xargs
para analizarla.
Considerar:
array=( )
while IFS= read -r -d ''''; do
array+=( "$REPLY" )
done < <(xargs printf ''%s/0'' <<<"$ARGS")
swap "${array[@]}"
... colocará el contenido analizado de
$ARGS
en la matriz de la
array
.
Si desea leer de un archivo, sustituya
<filename
de
<filename
por
<<<"$ARGS"
.
xargs, compatible con POSIX
Si está intentando escribir código compatible con POSIX sh, esto se vuelve más complicado. (Voy a asumir la entrada del archivo aquí para reducir la complejidad):
# This does not work with entries containing literal newlines; you need bash for that.
run_with_args() {
while IFS= read -r entry; do
set -- "$@" "$entry"
done
"$@"
}
xargs printf ''%s/n'' <argfile | run_with_args ./swap
Estos enfoques son más seguros que ejecutar
xargs ./swap <argfile
medida en que arrojará un error si hay argumentos más o más largos de los que se pueden acomodar, en lugar de ejecutar argumentos en exceso como comandos separados.
Python shlex, en lugar de xargs, con bashismos
Si necesita un análisis POSIX sh más preciso que los implementos
xargs
, considere usar el módulo Python
shlex
lugar:
shlex_split() {
python -c ''
import shlex, sys
for item in shlex.split(sys.stdin.read()):
sys.stdout.write(item + "/0")
''
}
while IFS= read -r -d ''''; do
array+=( "$REPLY" )
done < <(shlex_split <<<"$ARGS")
Las comillas incrustadas no protegen los espacios en blanco;
son tratados literalmente
Use una matriz en
bash
:
args=( "hi there" test)
./swap "${args[@]}"
En el shell POSIX, está atascado usando
eval
(por eso la mayoría de los shells admiten matrices).
args=''"hi there" test''
eval "./swap $args"
Como de costumbre, asegúrese de conocer el contenido de
$args
y comprender cómo se analizará la cadena resultante antes de usar
eval
.
Puede que este no sea el enfoque más sólido, pero es simple y parece funcionar para su caso:
## demonstration matching the question
$ ( ARGS=''"hi there" test'' ; ./swap ${ARGS} )
there" "hi
## simple solution, using ''xargs''
$ ( ARGS=''"hi there" test'' ; echo ${ARGS} |xargs ./swap )
test hi there