Pourquoi l'écho ignore-t-il mes caractères de citation?

24

La echocommande n'inclut pas le texte complet que je lui donne. Par exemple, si je le fais:

   $ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `    '

Il génère:

echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk {print } `

Les guillemets simples ( ') que j'avais dans ma commande echo ne sont pas inclus. Si je passe aux guillemets doubles:

$ echo " echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `  "

echone produit rien du tout! Pourquoi oublie-t-il les guillemets simples dans le premier exemple et ne produit-il rien dans le second? Comment puis-je le faire sortir exactement ce que j'ai tapé?

Michael Mrozek
la source
3
Je ne sais pas exactement ce que vous essayez de réaliser ici. Pourquoi imbriquez-vous une déclaration d'écho dans une autre? Ou essayez-vous littéralement d'imprimer une commande (pour un exemple ou tel)?
Andy Smith
J'ai besoin d'insérer la sortie d'écho dans un autre fichier
Vous devez expliquer ce que vous essayez de faire. Tels que "Je veux ajouter le texte suivant:"… ​​"à un fichier."
MikeyB
2
J'essaie de modifier cela depuis 5 minutes, mais je ne sais vraiment pas quelle est la sortie souhaitée. Edit : Ok, je pense que j'ai une supposition maintenant, alors j'ai essayé d'éditer. Si ce n'est pas ce que vous vouliez, faites le moi savoir
Michael Mrozek
1
@Michael - Wow - Si je pouvais vous donner un représentant pour votre montage, je ferais du bon travail, ce qui en fait une excellente question!
Randall

Réponses:

33

Votre shell interprète les guillemets, à la fois 'et ", avant même qu'ils ne parviennent à echo. Je mets généralement des guillemets autour de mon argument pour faire écho même s'ils ne sont pas nécessaires; par exemple:

$ echo "Hello world"
Hello world

Ainsi, dans votre premier exemple, si vous souhaitez inclure des guillemets littéraux dans votre sortie, vous devez soit les échapper:

$ echo \'Hello world\'
'Hello world'

Ou ils doivent déjà être utilisés dans un argument cité (mais ce ne peut pas être le même type de citation, ou vous devrez quand même y échapper):

$ echo "'Hello world'"
'Hello world'

$ echo '"Hello world"'
"Hello world"

Dans votre deuxième exemple, vous exécutez une substitution de commande au milieu de la chaîne:

grep  $ARG  /var/tmp/setfile  | awk {print $2}

Les choses qui commencent par $sont également gérées spécialement par le shell - il les traite comme des variables et les remplace par leurs valeurs. Étant donné que très probablement aucune de ces variables n'est définie dans votre shell, il s'exécute simplement

grep /var/tmp/setfile | awk {print}

Étant donné qu'il grepne voit qu'un seul argument, il suppose que cet argument est le modèle que vous recherchez et que l'endroit où il doit lire les données est stdin, il bloque donc l'attente de l'entrée. C'est pourquoi votre deuxième commande semble se bloquer.

Cela ne se produira pas si vous citez l'argument (ce qui explique pourquoi votre premier exemple a presque fonctionné), c'est donc une façon d'obtenir la sortie souhaitée:

echo \'' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `    '\'

Vous pouvez également le mettre entre guillemets, mais vous devrez ensuite échapper le $s pour que le shell ne les résout pas en tant que variables, et les astuces pour que le shell n'exécute pas immédiatement la substitution de commande:

echo "' echo PARAM=\`  grep  \$ARG  /var/tmp/setfile  | awk '{print \$2}' \`    '"
Michael Mrozek
la source
7

Je ne vais pas entrer dans les détails pour expliquer pourquoi vos tentatives se comportent comme elles le font, car la réponse de Michael Mrozek couvre bien cela. En un mot, tout entre les guillemets simples ( '…') est interprété littéralement (et en particulier le premier 'marque la fin de la chaîne littérale), tandis que `et $conservent leur signification particulière entre "…".

Il n'y a pas de guillemets dans les guillemets simples, vous ne pouvez donc pas mettre de guillemet simple dans une chaîne entre guillemets simples. Il existe cependant un idiome qui lui ressemble:

echo 'foo'\''bar'

Cela s'imprime foo'bar, car l'argument to echoest constitué de la chaîne de trois caractères entre guillemets simples foo, concaténée avec le caractère unique '(obtenue en protégeant le caractère 'de sa signification particulière par le biais du précédent \), concaténée avec la chaîne de trois caractères entre guillemets simples bar. Donc, bien que ce ne soit pas tout à fait ce qui se passe dans les coulisses, vous pouvez penser '\''à un moyen d'inclure une citation unique dans une chaîne entre guillemets simples.

Si vous souhaitez imprimer des chaînes multilignes complexes, un meilleur outil est le document ici . Un document ici se compose des deux caractères <<suivis d'un marqueur tel que <<EOF, puis de quelques lignes de texte, puis du marqueur de fin seul sur sa ligne. Si le marqueur est cité de quelque manière que ce soit ( 'EOF'ou "EOF"ou \EOFou 'E "" OF' ou…), alors le texte est interprété littéralement (comme à l'intérieur de guillemets simples, sauf que même 'est un caractère ordinaire). Si le marqueur n'est pas du tout cité, le texte est interprété comme dans une chaîne entre guillemets doubles, en $\`conservant leur statut spécial (mais "et les sauts de ligne sont interprétés littéralement).

cat <<'EOF'
echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `
EOF
Gilles 'SO- arrête d'être méchant'
la source
1

D'accord, cela fonctionnera: -

echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '\''{print $2}'\'' `    '

Le tester ici: -

[asmith@yagi ~]$ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '\''{print $2}'\'' `    '
 echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' ` 
Andy Smith
la source
0

La suggestion de Gilles d'utiliser un document ici est vraiment sympa, et fonctionne même dans des langages de script comme Perl. Comme exemple spécifique basé sur sa réponse à la question du PO,

use strict;
my $cmd = q#cat <<'EOF' # . "\n" .
          q#echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `# . "\n" .
          q#EOF# ;
open (my $OUTPUT, "$cmd |");
print $_ while <$OUTPUT>;
close $OUTPUT;

Certes, cet exemple est un peu artificiel, mais j'ai trouvé que c'était une technique utile pour, disons, envoyer des instructions SQL à psql(au lieu de cat).

(Notez que tout caractère non alphanumérique non espace peut être utilisé à la place de #dans la construction générique citée ci-dessus, mais le hachage semble bien ressortir pour cet exemple et n'est pas traité comme un commentaire.)

Randall
la source
-1

Prenons votre commande

$ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print $2}' `    '

et d'abord le diviser en mots, en utilisant un espace blanc sans guillemets comme délimiteur:

Arg[1]=“' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print”
Arg[2]=“$2}' `    '”

Ensuite, nous faisons l'expansion des paramètres: développez $…mais pas à l'intérieur '…'. Puisque $2est vide (sauf si vous le définissez via par exemple set -- …), il sera remplacé par une chaîne vide.

Arg[1]=“' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '{print”
Arg[2]=“}' `    '”

Ensuite, supprimez le devis.

Arg[1]=“ echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk {print”
Arg[2]=“} `    ”

Ces arguments sont ensuite passés à echo, qui les affichera l'un après l'autre, séparés par un seul espace.

“ echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk {print } `    ”

J'espère que cette instruction étape par étape rend le comportement observé plus clair. Pour éviter le problème, échappez aux guillemets simples comme celui-ci:

$ echo ' echo PARAM=`  grep  $ARG  /var/tmp/setfile  | awk '\''{print $2}'\'' `    '

Cela mettra fin à la chaîne entre guillemets simples, puis ajoutera un guillemet simple (échappé) au mot, puis démarrera une nouvelle chaîne entre guillemets simples, le tout sans séparer le mot.

MvG
la source