bash - programacion - sentencias de control en shell script
Preservando comillas en los parámetros de la función bash (8)
Aunque la answer es correcta, y no hay citas para preservar, se puede intentar deducir si las citas fueron necesarias y, por lo tanto, se pasaron originalmente. Personalmente usé esta función de requote
cuando necesitaba una prueba en mis registros de que un comando se ejecutó con la cita correcta:
function requote() {
local res=""
for x in "${@}" ; do
# try to figure out if quoting was required for the $x:
grep -q "[[:space:]]" <<< "$x" && res="${res} ''${x}''" || res="${res} ${x}"
done
# remove first space and print:
sed -e ''s/^ //'' <<< "${res}"
}
Y aquí es cómo lo uso:
CMD=$(requote "${@}")
# ...
echo "${CMD}"
Lo que me gustaría hacer es tomar, como entrada a una función, una línea que puede incluir comillas (simple o doble) y repetir esa línea exactamente como se proporcionó a la función. Por ejemplo:
function doit {
printf "%s " ${@}
eval "${@}"
printf " # [%3d]/n" ${?}
}
Que, dada la siguiente entrada
doit VAR=42
doit echo ''single quote $VAR''
doit echo "double quote $VAR"
Rinde lo siguiente:
VAR=42 # [ 0]
echo single quote $VAR # [ 0]
echo double quote 42 # [ 0]
Por lo tanto, la semántica de la expansión de la variable se conserva como esperaba, pero no puedo obtener el formato exacto de la línea tal como se proporcionó a la función. Lo que me gustaría es que doit echo ''single quote $VAR''
resulte en echo ''single quote $VAR''
.
Estoy seguro de que esto tiene que ver con bash procesando los argumentos antes de pasarlos a la función; Solo estoy buscando una forma de evitar eso (si es posible).
Editar
Entonces, lo que pretendía era ocultar la ejecución de un script mientras proporcionaba una réplica exacta de la ejecución que podía usarse como herramienta de diagnóstico, incluido el estado de salida de cada paso.
Si bien puedo obtener el comportamiento deseado descrito anteriormente haciendo algo como
while read line ; do
doit ${line}
done < ${INPUT}
Ese enfoque falla ante las estructuras de control (es decir, if
, while
, etc.). Pensé en usar set -x
pero eso también tiene sus limitaciones: "
convierte en ''
y el estado de salida no es visible para los comandos que fallan.
Bash eliminará la comilla cuando pase una cadena con comilla como argumento de línea de comando. La cita simplemente ya no existe cuando la cadena pasa a su script. No tiene forma de saber que hay una comilla simple o comilla doble.
Lo que probablemente puedas hacer es algo así:
doit VAR=42
doit echo /'single quote $VAR/'
doit echo /"double quote $VAR/"
En tu guión obtienes
echo ''single quote $VAR''
echo "double quote $VAR"
O haz esto
doit VAR=42
doit echo ''single quote $VAR''
doit echo ''"double quote $VAR"''
En tu guión obtienes
echo single quote $VAR
echo "double quote $VAR"
El shell interpretará las comillas y el $
antes de pasarlo a su función. No hay mucho que su función pueda hacer para recuperar los caracteres especiales, porque no tiene forma de saber (en el ejemplo de comillas dobles) si 42
fue codificado o si provino de una variable. Tendrá que escapar de los caracteres especiales si desea que sobrevivan el tiempo suficiente para cumplir con su función.
Esta:
ponerApostrofes1 ()
{
for (( i=1; i<=$#; i++ ));
do
eval VAR="/${$i}";
echo /'"${VAR}"/';
done;
return;
}
Como ejemplo tiene problemas cuando los parámetros tienen apóstrofes.
Esta función:
ponerApostrofes2 ()
{
for ((i=1; i<=$#; i++ ))
do
eval PARAM="/${$i}";
echo -n /'${PARAM///'//'///'/'}/''' '';
done;
return
}
resuelve el problema mencionado y puede usar parámetros que incluyen apóstrofes en el interior, como "Porky''s", y devuelve, aparentemente (?), la misma cadena de parámetros cuando se cita cada parámetro; Si no, lo cita. Sorprendentemente, no entiendo por qué, si lo usa recursivamente, no devuelve la misma lista, pero cada parámetro se cita nuevamente. Pero si haces eco de cada uno recuperas el parámetro original.
Ejemplo:
$ ponerApostrofes2 ''aa aaa'' ''bbbb b'' ''c''
''aa aaa'' ''bbbb b'' ''c''
$ ponerApostrofes2 $(ponerApostrofes2 ''aa aaa'' ''bbbb b'' ''c'' )
''''/'''aa'' ''aaa''/''''' ''''/'''bbbb'' ''b''/''''' ''''/'''c''/'''''
Y:
$ echo ''''/'''bbbb'' ''b''/'''''
''bbbb b''
$ echo ''''/'''aa'' ''aaa''/'''''
''aa aaa''
$ echo ''''/'''c''/'''''
''c''
Y éste:
ponerApostrofes3 ()
{
for ((i=1; i<=$#; i++ ))
do
eval PARAM="/${$i}";
echo -n ${PARAM///'//'///'/'} '' '';
done;
return
}
devolver un nivel de cotización menos, tampoco funciona, ni alternar ambos recursivamente.
Estaba en una posición similar a usted en que necesitaba una secuencia de comandos para envolver un comando existente y pasar argumentos conservando las citas.
Se me ocurrió algo que no conserva la línea de comandos exactamente como se escribió, pero que pasa los argumentos correctamente y te muestra cuáles eran.
Aquí está mi script configurado para la sombra ls
:
CMD=ls
PARAMS=""
for PARAM in "$@"
do
PARAMS="${PARAMS} /"${PARAM}/""
done
echo Running: ${CMD} ${PARAMS}
bash -c "${CMD} ${PARAMS}"
echo Exit Code: $?
Y esta es una salida de muestra:
$ ./shadow.sh missing-file "not a file"
Running: ls "missing-file" "not a file"
ls: missing-file: No such file or directory
ls: not a file: No such file or directory
Exit Code: 1
Entonces, como pueden ver, agrega citas que originalmente no estaban allí, pero sí preserva los argumentos con espacios en los que es lo que necesitaba.
La razón por la que esto sucede es porque bash interpreta los argumentos, como pensaste. Las citas simplemente ya no están más cuando llama a la función, por lo que esto no es posible. Funcionó en DOS porque los programas podrían interpretar la línea de comandos por sí mismos, ¡no es que te ayude!
Si el shell de uno no admite la sustitución de patrones, es decir, ${param/pattern/string}
, se puede usar la siguiente expresión sed
para citar de forma segura cualquier cadena de manera que se eval
en un solo parámetro de nuevo:
sed "s/''/''////'''/g;1s/^/''/;/$s//$/''/"
Combinando esto con printf
es posible escribir una pequeña función que tome cualquier lista de cadenas producidas por la expansión del nombre de archivo o "$@"
y la convierta en algo que pueda pasarse de forma segura a eval
para expandirlo en argumentos para otro comando mientras se está seguro. Conservando la separación de parámetros.
# Usage: quotedlist=$(shell_quote args...)
#
# e.g.: quotedlist=$(shell_quote *.pdf) # filenames with spaces
#
# or: quotedlist=$(shell_quote "$@")
#
# After building up a quoted list, use it by evaling it inside
# double quotes, like this:
#
# eval "set -- $quotedlist"
# for str in "$@"; do
# # fiddle "${str}"
# done
#
# or like this:
#
# eval "/$a_command $quotedlist /$another_parameter"
#
shell_quote()
{
local result=''''
local arg
for arg in "$@" ; do
# Append a space to our result, if necessary
#
result=${result}${result:+ }
# Convert each embedded '' to /' , then insert '' at the
# beginning of the line, and append '' at the end of
# the line.
#
result=${result}$(printf "%s/n" "$arg" | /
sed -e "s/''/''////'''/g" -e "1s/^/''/" -e "/$s//$/''/")
done
# use printf(1) instead of echo to avoid weird "echo"
# implementations.
#
printf "%s/n" "$result"
}
Puede ser más fácil (y tal vez más seguro, es decir, evitar eval
) en algunas situaciones usar un carácter "imposible" como separador de campo y luego usar IFS
para controlar la expansión del valor nuevamente.
doit echo "''single quote $VAR''"
doit echo ''"double quote $VAR"''
Ambos funcionarán.
Bash solo eliminará el conjunto externo de comillas al ingresar a la función.