bash shell sh

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 y shlex 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