4 votes

Faire en sorte que bash respecte les guillemets lors du découpage des mots de la sortie du sous-shell.

J'ai une commande qui produit certains paramètres que je veux transmettre à une autre commande. Cependant, lorsque j'exécute la commande dans un sous-shell, la sortie est sujette à division des mots à moins que je ne cite la totalité du texte, auquel cas il s'agit d'un seul mot.

Je veux que la sortie du sous-shell ait des mots séparés, mais je veux que la séparation des mots respecte les guillemets, et ce n'est pas le cas. Existe-t-il un moyen (autre que eval ) pour que la sortie du sous-shell soit divisée en mots mais en respectant les guillemets ?

Détails

Compte tenu de la args La commande est définie comme suit

#!/bin/sh -
printf "%d args:" "$#"
printf " <%s>" "$@"
echo

Je peux courir

$ args 'foo bar' 'one two'
2 args: <foo bar> <one two>

mais je ne trouve pas le moyen de faire passer 2 args à un sous-shell comme ça.

$ echo "\"'foo bar'\" 'one two'"
"'foo bar'" 'one two'
$ args $(echo "\"'foo bar'\" 'one two'")
4 args: <"'foo> <bar'"> <'one> <two'>
$ args "$(echo "\"'foo bar'\" 'one two'")"
1 args: <"'foo bar'" 'one two'>

Bien sûr, je peux utiliser eval

$  eval args $(echo "'foo bar' 'one two'")
2 args: <foo bar> <one two>

mais eval est dangereux et introduit toutes sortes d'autres possibilités effrayantes pour que les choses tournent mal. Je ne veux pas que l'expansion des paramètres ou le globbing se reproduisent, etc. Je veux simplement que le découpage des mots respecte les guillemets. N'y a-t-il vraiment aucun moyen de faire cela dans bash ?

9voto

rwc9u Points 532

Bash ne dispose pas vraiment d'un bon moyen d'analyser une chaîne de caractères en sous-chaînes, tout en respectant les guillemets. Que cela provienne d'une expansion de commande (c'est-à-dire , $( ) -- ce que je pense que vous appelez une sous-coquille) ou une expansion variable simple ( $varname ).

  • Si vous citez deux fois l'expansion, aucun fractionnement n'est effectué.
  • Si vous ne citez pas deux fois l'expansion, elle se divise sur les espaces, mais ne fait pas attention aux guillemets ou aux échappatoires. Il essaie également d'étendre tout ce qui ressemble à un caractère générique de nom de fichier, ce qui peut être comique et/ou tragique.
  • Si vous utilisez eval sur une expansion à double guillemet, il analyse tous Shell syntaxe, y compris d'autres expansions de commandes et de variables, les redirections, les commandes multiples avec ; ou & etc. Beaucoup de possibilités de mauvais résultats ici.
  • Si vous utilisez eval sur une expansion non citée, vous obtenez l'effet de scission sur les espaces blancs et d'expansion sur les gardes, suivi de par une analyse régulière et complète. Presque tout peut mal tourner ici.
  • read -a est le meilleur d'un mauvais lot. Il ne respecte pas du tout les guillemets, mais au moins il ne développe pas les caractères génériques des noms de fichiers.

Donc bash lui-même ne peut pas faire ça. Mais xargs peut -- son analyse par défaut des mots séparés respecte les guillemets et les échappatoires, donc selon la situation, vous pouvez l'utiliser directement :

$ echo "\"'foo bar'\" 'one two'" | xargs args
2 args: <'foo bar'> <one two>

Il y a quelques problèmes potentiels avec cela : D'une part, en fonction de la quantité de sortie, xargs pourrait décider de les répartir entre plusieurs exécutions de la commande. Vous pouvez ajuster cela dans une certaine mesure avec la fonction -n , -s y -x mais ce n'est pas entièrement satisfaisant.

Un autre problème possible est que si la commande est en fait une fonction Shell, une commande complexe, ou un builtin que vous voulez exécuter dans le Shell actuel, cela ne fonctionnera pas. Vous pouvez l'adapter, mais c'est désordonné ; vous devez utiliser la fonction xargs printf '%s\0' pour convertir en une séquence de chaînes de caractères délimitées par des zéros, puis utilisez un fichier while IFS= read -r -d '' pour le convertir en un tableau bash, et enfin vous pouvez faire quelque chose avec le tableau :

$ argarray=()
$ while IFS= read -r -d '' arg; do argarray+=("$arg"); done < <(echo "\"'foo bar'\" 'one two'" | xargs printf '%s\0')
$ args "${argarray[@]}"
2 args: <'foo bar'> <one two>

Notez que cela utilise une substitution de processus avec <( ) . Il s'agit d'une fonctionnalité propre à bash, qui ne fonctionnera même pas lorsque bash est en mode sh-emulation. Vous devez donc commencer votre script avec un shebang bash explicite ( #!/bin/bash ou #!/usr/bin/env bash ) (et ne remplacez pas le shebang en exécutant le script avec sh scriptname ).

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X