web-dev-qa-db-fra.com

Comment conserver les guillemets dans les arguments Bash?

J'ai un script Bash dans lequel je veux conserver des guillemets dans les arguments passés. 

Exemple:

./test.sh this is "some test"

ensuite, je souhaite utiliser ces arguments et les réutiliser, y compris des guillemets autour de la liste des arguments.

J'ai essayé d'utiliser \"$@\", mais cela supprime les guillemets dans la liste.

Comment puis-je accomplir cela?

61
zlack

l'utilisation de "$@" remplacera les arguments sous forme de liste, sans les scinder à nouveau en espaces (ils ont été scindés une fois lorsque le script Shell a été appelé), ce qui correspond généralement à ce que vous voulez si vous voulez simplement rediffuser les arguments à un autre programme .

Qu'essayez-vous de faire et de quelle manière cela ne fonctionne-t-il pas?

72
Chris Dodd

La réponse de Yuku ne fonctionne que si vous êtes le seul utilisateur de votre script, tandis que celle de Dennis Williamson est intéressante si vous souhaitez principalement imprimer les chaînes et que vous ne leur demandez pas de guillemets.

Voici une version qui peut être utilisée si vous souhaitez transmettre tous les arguments en tant qu'un grand argument quoted-string au paramètre -c de bash ou su:

#!/bin/bash
C=''
for i in "$@"; do 
    i="${i//\\/\\\\}"
    C="$C \"${i//\"/\\\"}\""
done
bash -c "$C"

Ainsi, tous les arguments sont entourés d'une citation (inoffensif s'il n'y figurait pas auparavant, mais nous échappons également à toute évasion, puis à toute citation déjà présente dans un argument (la syntaxe ${var//from/to} effectue une substitution de sous-chaîne globale).

Vous pouvez bien sûr ne citer que des éléments contenant déjà des espaces, mais cela n’a aucune importance ici. Un utilitaire de script tel que celui-ci consiste à pouvoir avoir un certain ensemble prédéfini de variables d’environnement (ou, avec su, d’exécuter des tâches en tant qu’utilisateur donné, sans ce fouillis de tout ce qui a été cité).


Mise à jour: j'ai récemment eu des raisons de faire cela de manière POSIX avec un forking minimal, ce qui a conduit à ce script (le dernier printf y affiche la ligne de commande utilisée pour appeler le script, que vous devriez pouvoir copier-coller pour invoquer avec des arguments équivalents):

#!/bin/sh
C=''
for i in "$@"; do
    case "$i" in
        *\'*)
            i=`printf "%s" "$i" | sed "s/'/'\"'\"'/g"`
            ;;
        *) : ;;
    esac
    C="$C '$i'"
done
printf "$0%s\n" "$C"

Je suis passé à '' car les shells interprètent également des choses comme $ et !! entre guillemets ""-.

34
unhammer

S'il est prudent de supposer qu'un argument contenant des espaces doit avoir été (et devrait être) cité, vous pouvez les ajouter comme ceci:

#!/bin/bash
whitespace="[[:space:]]"
for i in "$@"
do
    if [[ $i =~ $whitespace ]]
    then
        i=\"$i\"
    fi
    echo "$i"
done

Voici un exemple de parcours:

$ ./argtest abc def "ghi jkl" $'mno\tpqr' $'stu\nvwx'
abc
def
"ghi jkl"
"mno    pqr"
"stu
vwx"

Vous pouvez aussi insérer onglets littéraux et retours à la ligne en utilisant Ctrl-VTab et Ctrl-VCtrl-J entre guillemets simples ou doubles au lieu d'utiliser des échappements dans $'...'.

Une note sur insertion caractères dans Bash: Si vous utilisez des raccourcis clavier Vi (set -o vi) dans Bash (Emacs est la valeur par défaut - set -o emacs), vous devez être dans insérer mode pour insérer des caractères. En mode Emacs, vous êtes toujours en mode insertion.

32

Il y a deux manières sûres de faire ceci:

1. Expansion des paramètres du shell : ${variable@Q}:

Lors du développement d'une variable via ${variable@Q}:

Le développement est une chaîne qui correspond à la valeur du paramètre cité dans un format pouvant être réutilisé en tant qu'entrée.

Exemple:

$ expand-q() { for i; do echo ${i@Q}; done; }  # Same as for `i in "$@"`...
$ expand-q Word "two words" 'new
> line' "single'quote" 'double"quote'
Word
'two words'
$'new\nline'
'single'\''quote'
'double"quote'

2. printf %q "$quote-me"

printf supporte les citations en interne. L'entrée du manuel pour printf dit:

%q Force printf à générer l'argument correspondant dans un format pouvant être réutilisé comme entrée Shell.

Exemple:

$ cat test.sh 
#!/bin/bash
printf "%q\n" "$@"
$ 
$ ./test.sh this is "some test" 'new                                                                                                              
>line' "single'quote" 'double"quote'
this
is
some\ test
$'new\nline'
single\'quote
double\"quote
$

Notez que la 2ème manière est un peu plus propre si vous affichez le texte cité à un humain.

Connexes: pour bash, POSIX sh et zsh: Chaîne de devis avec des guillemets simples plutôt que des barres obliques inverses

16
Tom Hale

Comme Tom Hale l’a dit, l’un des moyens de procéder consiste à utiliser printf en utilisant %q pour quote-escape.

Par exemple:

send_all_args.sh

#!/bin/bash
if [ "$#" -lt 1 ]; then
 quoted_args=""
else
 quoted_args="$(printf " %q" "${@}")"
fi

bash -c "$( dirname "${BASH_SOURCE[0]}" )/receiver.sh${quoted_args}"

send_fewer_args.sh

#!/bin/bash
if [ "$#" -lt 2 ]; then
 quoted_last_args=""
else
 quoted_last_args="$(printf " %q" "${@:2}")"
fi

bash -c "$( dirname "${BASH_SOURCE[0]}" )/receiver.sh${quoted_last_args}"

destinataire.sh

#!/bin/bash
for arg in "$@"; do
  echo "$arg"
done

Exemple d'utilisation:

$ ./send_all_args.sh
$ ./send_all_args.sh a b
a
b
$ ./send_all_args.sh "a' b" 'c "e '
a' b
c "e 
$ ./send_fewer_args.sh
$ ./send_fewer_args.sh a
$ ./send_fewer_args.sh a b
b
$ ./send_fewer_args.sh "a' b" 'c "e '
c "e 
$ ./send_fewer_args.sh "a' b" 'c "e ' 'f " g'
c "e 
f " g
6
Gary S. Weaver

J'avais besoin de ça pour transmettre tous les arguments à un autre interprète .. Ce qui a bien fonctionné pour moi est:

bash -c "$(printf ' %q' "$@")"

Exemple (sous le nom forward.sh):

$ ./forward.sh echo "3 4"
3 4
$ ./forward.sh bash -c "bash -c 'echo 3'"
3

(Bien sûr, le script que j'utilise est plus complexe, impliquant dans mon cas Nohup et les redirections, etc., mais c'est la partie clé.)

5
isarandi

Mon problème était similaire et j'ai utilisé des idées mitigées publiées ici.

Nous avons un serveur avec un script PHP qui envoie des courriels. Et puis nous avons un deuxième serveur qui se connecte au premier serveur via SSH et l'exécute.

Le nom du script est le même sur les deux serveurs et les deux sont en réalité exécutés via un script bash.

Sur le script bash du serveur 1 (local), nous avons juste:

/usr/bin/php /usr/local/myscript/myscript.php "$@"

Cela réside sur /usr/local/bin/myscript et est appelé par le serveur distant. Cela fonctionne très bien même pour les arguments avec des espaces.

Mais sur le serveur distant, nous ne pouvons pas utiliser la même logique car le 1er serveur ne recevra pas les guillemets de "$@". J'ai utilisé les idées de JohnMudd et Dennis Williamson pour recréer le tableau d'options et de paramètres avec les citations. J'aime l'idée d'ajouter des citations échappées uniquement lorsque l'élément contient des espaces.

Donc, le script distant est exécuté avec:

CSMOPTS=()
whitespace="[[:space:]]"
for i in "$@"
do
    if [[ $i =~ $whitespace ]]
    then
        CSMOPTS+=(\"$i\")
    else
        CSMOPTS+=($i)
    fi
done

/usr/bin/ssh "$USER@$SERVER" "/usr/local/bin/myscript ${CSMOPTS[@]}"

Notez que j'utilise "${CSMOPTS[@]}" pour transmettre le tableau d'options au serveur distant.

Merci pour tous ceux qui ont posté ici! Cela m'a vraiment aidé! :)

4
Gus Neves

Exemple modifié de unhammer pour utiliser array.

printargs() { printf "'%s' " "$@"; echo; };  # http://superuser.com/a/361133/126847

C=()
for i in "$@"; do
    C+=("$i")  # Need quotes here to append as a single array element.
done

printargs "${C[@]}"  # Pass array to a program as a list of arguments.                               
4
JohnMudd

Les citations sont interprétées par bash et ne sont pas stockées dans des arguments de ligne de commande ou des valeurs de variables.

Si vous voulez utiliser des arguments cités, vous devez les citer chaque fois que vous les utilisez:

val="$3"
echo "Hello World" > "$val"
3
mouviciel

Il suffit d'utiliser:

"${@}"

Par exemple:

# cat t2.sh
for I in "${@}"
do
   echo "Param: $I"
done
# cat t1.sh
./t2.sh "${@}"

# ./t1.sh "This is a test" "This is another line" a b "and also c"
Param: This is a test
Param: This is another line
Param: a
Param: b
Param: and also c
1
FrameGrace

Si vous devez transmettre tous les arguments à bash à partir d'un autre langage de programmation (par exemple, si vous souhaitez exécuter bash -c ou emit_bash_code | bash), utilisez ceci:

  • échappe à tous les caractères entre guillemets que tu as avec '\''.
  • puis, entourez le résultat avec des citations singulières

L'argument de abc'def sera donc converti en 'abc'\''def'. Les caractères '\'' sont interprétés comme suit: la citation existante est terminée par la première citation, puis la citation simple au singulier échappée \' vient, puis la nouvelle citation commence.

0
VasiliNovikov

Comme Gary S. Weaver l'a montré dans ses astuces sur le code source, le truc consiste à appeler bash avec le paramètre '-c' puis à citer le suivant.

par exemple.

bash -c "<your program> <parameters>"

ou

docker exec -it <my docker> bash -c "$SCRIPT $quoted_args"
0
Lars

Oui, il semble qu'il ne soit pas possible de conserver les citations, mais pour le problème que je traitais, ce n'était pas nécessaire. 

J'ai une fonction bash qui va chercher dans le dossier de manière récursive et grep une chaîne, le problème est de passer une chaîne qui contient des espaces, comme "trouver cette chaîne" Passer ceci au script bash prendra alors l'argument de base $ n et le passera à grep, ce qui signifie que grep pense qu'il s'agit d'arguments différents. La façon dont j'ai résolu ce problème en utilisant le fait que lorsque vous citez bash pour appeler la fonction, elle regroupe les éléments des guillemets en un seul argument. Je devais juste décorer cet argument avec des guillemets et le passer à la commande grep. 

Si vous connaissez l'argument que vous recevez dans bash et qui nécessite des guillemets pour l'étape suivante, vous pouvez simplement décorer avec des guillemets. 

0
Tom Orsi