variable tab bash posix printf quotes sh

variable - linux bash printf tab



POSIX sh equivalente para Bash''s printf% q (4)

Supongamos que tengo un script #!/bin/sh que puede tomar una variedad de parámetros posicionales, algunos de los cuales pueden incluir espacios, cualquiera de los dos tipos de comillas, etc. Quiero repetir "$@" y para cada argumento cualquiera de los dos procesos Inmediatamente de alguna manera, o guardarlo para más tarde. Al final de la secuencia de comandos, quiero iniciar (tal vez exec ) otro proceso, pasando algunos de estos parámetros con todos los caracteres especiales intactos.

Si no estuviera procesando los parámetros, othercmd "$@" funcionaría bien, pero necesito extraer algunos parámetros y procesarlos un poco.

Si pudiera asumir Bash, entonces podría usar printf %q para calcular versiones citadas de argumentos que podría eval más adelante, pero esto no funcionaría en, por ejemplo, el Dash de Ubuntu ( /bin/sh ).

¿Hay algún equivalente a printf %q que se pueda escribir en un script de shell Bourne simple, utilizando solo los servicios integrados y las utilidades definidas por POSIX, por ejemplo, como una función que podría copiar en un script?

Por ejemplo, un script que intenta ls sus argumentos en orden inverso:

#!/bin/sh args= for arg in "$@" do args="''$arg'' $args" done eval "ls $args"

Funciona para muchos casos:

$ ./handle goodbye "cruel world" ls: cannot access cruel world: No such file or directory ls: cannot access goodbye: No such file or directory

pero no cuando se usa:

$ ./handle goodbye "cruel''st world" ./handle: 1: eval: Syntax error: Unterminated quoted string

y lo siguiente funciona bien pero se basa en Bash:

#!/bin/bash args= for arg in "$@" do printf -v argq ''%q'' "$arg" args="$argq $args" done eval "ls $args"


Creo que esto es POSIX. Funciona borrando $@ después de expandirlo para el bucle for, pero solo una vez para que podamos construirlo de forma iterativa de nuevo (a la inversa) usando set .

flag=0 for i in "$@"; do [ "$flag" -eq 0 ] && shift $# set -- "$i" "$@" flag=1 done echo "$@" # To see that "$@" has indeed been reversed ls "$@"

Me doy cuenta de que revertir los argumentos fue solo un ejemplo, pero es posible que pueda usar este truco de set -- "$arg" "$@" o set -- "$@" "$arg" en otras situaciones.

Y sí, me doy cuenta de que podría haber reimplementado (mal) el empuje de ormaaj.


Esto es absolutamente factible.

La respuesta que ve por Jesse Glick está aproximadamente allí, pero tiene un par de errores, y tengo algunas alternativas más para su consideración, ya que este es un problema que me encontré más de una vez.

Primero, y es posible que ya sepa esto, echo es una mala idea, uno debería usar printf en su lugar, si el objetivo es la portabilidad: "echo" tiene un comportamiento indefinido en POSIX si el argumento que recibe es "-n", y en la práctica algunos implementaciones de echo treat -n como una opción especial, mientras que otras simplemente lo tratan como un argumento normal para imprimir. Así que eso se convierte en esto:

esceval() { printf %s "$1" | sed "s/''/''/"''/"''/g" }

Alternativamente, en lugar de escapar de comillas simples incrustadas, conviértelas en:

''"''"''

.. En vez de eso podrías convertirlos en:

''/'''

Las diferencias estilísticas supongo (imagino que la diferencia de rendimiento es insignificante de todas formas, aunque nunca lo he probado) La cadena sed resultante se ve así:

esceval() { printf %s "$1" | sed "s/''/''////'''/g" }

(Son cuatro barras invertidas porque las comillas dobles se tragan dos de ellas, y dejando dos, y luego sed traga una, dejando solo una. Personalmente, me parece más legible, así que eso es lo que usaré en el resto de los ejemplos que involucran eso, pero ambos deben ser equivalentes.)

PERO, todavía tenemos un error: la sustitución del comando eliminará al menos una (pero en muchos casos TODOS) de las nuevas líneas finales de la salida del comando (no todos los espacios en blanco, solo las nuevas líneas específicamente). Por lo tanto, la solución anterior funciona a menos que tenga nuevas líneas al final de un argumento. Entonces perderás esa / esa nueva línea (s). La solución es obviamente simple: agregue otro carácter después del valor del comando real antes de enviar desde su función quote / esceval. Incidentalmente, ya teníamos que hacerlo de todos modos, porque necesitábamos iniciar y detener el argumento de escape con comillas simples. Honestamente, no entiendo por qué no se hizo para empezar. Tienes dos alternativas:

esceval() { printf ''%s/n'' "$1" | sed "s/''/''////'''/g; 1 s/^/''/; $ s/$/''/" }

Esto asegurará que el argumento salga ya completamente escapado, sin necesidad de agregar más comillas simples al construir la cadena final. Esta es probablemente la cosa más cercana a una única versión en línea. Si está de acuerdo con tener una dependencia sed, puede detenerse aquí.

Si no está de acuerdo con la dependencia sed, pero está bien si asume que su shell es en realidad compatible con POSIX (todavía hay algunos por ahí, en particular el / bin / sh en Solaris 10 y versiones posteriores, que no podrá hacer esta próxima variante, pero casi todas las carcasas de las que debe preocuparse harán esto muy bien):

esceval() { printf /' UNESCAPED=$1 while : do case $UNESCAPED in */'*) printf %s "${UNESCAPED%%/'*}""''/'''" UNESCAPED=${UNESCAPED#*/'} ;; *) printf %s "$UNESCAPED" break esac done printf /' }

Podrías notar citas aparentemente redundantes aquí:

printf %s "${UNESCAPED%%/'*}""''/'''"

..este podría ser reemplazado por:

printf %s "${UNESCAPED%%/'*}''/'''"

La única razón por la que hago lo primero, es que una vez hubo shells Bourne que tenían errores al sustituir variables en cadenas entre comillas donde la cita alrededor de la variable no comenzó ni terminó exactamente donde sí lo hizo la sustitución de la variable. Por lo tanto, es un hábito paranoico de portabilidad mío. En la práctica, puedes hacer esto último, y no será un problema.

Si no desea obstruir la variable UNESCAPED en el resto de su entorno de shell, puede envolver todo el contenido de esa función en una subshell, así:

esceval() { ( printf /' UNESCAPED=$1 while : do case $UNESCAPED in */'*) printf %s "${UNESCAPED%%/'*}""''/'''" UNESCAPED=${UNESCAPED#*/'} ;; *) printf %s "$UNESCAPED" break esac done printf /' ) }

"Pero espera", dices: "¿Qué deseo hacer esto en MÚLTIPLES argumentos en un solo comando? Y quiero que la salida se vea agradable y legible para mí como usuario si la ejecuto desde la línea de comandos por cualquier motivo . "

Nunca temas, te tengo cubierto:

esceval() { case $# in 0) return 0; esac while : do printf "''" printf %s "$1" | sed "s/''/''////'''/g" shift case $# in 0) break; esac printf "'' " done printf "''/n" }

..o lo mismo, pero con la versión de shell solamente:

esceval() { case $# in 0) return 0; esac ( while : do printf "''" UNESCAPED=$1 while : do case $UNESCAPED in */'*) printf %s "${UNESCAPED%%/'*}""''/'''" UNESCAPED=${UNESCAPED#*/'} ;; *) printf %s "$UNESCAPED" break esac done shift case $# in 0) break; esac printf "'' " done printf "''/n" ) }

En esos últimos cuatro, podría colapsar algunas de las declaraciones externas de printf y resumir sus comillas simples en otra printf; las mantuve separadas porque creo que hace que la lógica sea más clara cuando puede ver las comillas simples de inicio y finalización en diferentes imprimir declaraciones.

También hay esta monstruosidad que hice, que es un relleno de polietileno que seleccionará entre las dos versiones anteriores dependiendo de si su shell parece ser capaz de soportar la sintaxis de sustitución de variables necesaria (aunque se ve horrible, porque la versión solo de shell tiene que estar dentro de una cadena evaluada para evitar que los shells incompatibles de barfing cuando la vean): https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh


Lo siguiente parece funcionar con todo lo que le he lanzado hasta ahora, incluidos los espacios, ambos tipos de citas y una variedad de otros metacaracteres y nuevas líneas incrustadas:

#!/bin/sh quote() { echo "$1" | sed "s/''/''/"''/"''/g" } args= for arg in "$@" do argq="''"`quote "$arg"`"''" args="$argq $args" done eval "ls $args"


Push Vea el léame para ver ejemplos.