1 votes

sed, en passant la valeur du groupe de capture à la sous-commande

Prenons l'exemple suivant :

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=\2/"

sorties

abc=123

c'est-à-dire la même chose que l'entrée. Nous avons tout fait correspondre jusqu'à ce que le symbole = comme la capture du groupe 1, et tout ce qui suit le symbole = comme le groupe de capture 2. Ceci est juste pour expliquer l'exemple.

Je peux invoquer la sous-commande à partir de sed comme ceci :

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=$(date)/"

produisant :

abc=Tue Nov 17 08:35:13 PM CET 2020

afin que je puisse appeler la fonction zéro-arité pour obtenir le remplacement.

Question : Que faire si je souhaite prendre le contenu d'un groupe de capture et appeler une commande pour obtenir un remplacement réel ? Dites :

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=$('\2 * 2' | bc)/"

produisant :

bash: \2 * 2: command not found
abc=123

Ce dont j'ai besoin, c'est de capturer du texte, et d'exécuter une commande dessus pour obtenir un remplacement. Comment faire ?

0voto

Kamil Maciorowski Points 57004

$(date) n'invoque pas de sous-commande à partir de sed ". Le Shell développe les mots entre guillemets. $(…) antes de sed est exécuté. La sortie de date apparaît dans la commande et c'est ce que sed obtient . Votre commande

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=$(date)/"

est devenu ceci :

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/\1=Tue Nov 17 08:35:13 PM CET 2020/"

et seulement alors sed a commencé. Avec cette approche, vous ne pouvez rien passer de sed pour date (ou bc ou autre) parce que l'intérieur de $(…) se termine avant sed commence.

Standard sed ne peut pas faire ce que vous voulez. GNU sed puede grâce à la e drapeau. Construisons un exemple avec GNU sed y bc .

echo abc=123 | sed "s/\([^=]*\)=\(.*\)/printf '%s' '\1='; printf '%s\n' '\2 * 2' | bc/e"
#                                      ----------------------------------------------

Sans e le fragment souligné serait un remplacement. Avec e il obtient exécuté dans un Shell et sa sortie devient le remplacement.

\n est là parce que dans mes tests bc n'a pas fonctionné avec des lignes incomplètes. Notez ceci \n est interprété par sed lui-même, printf obtiendra un véritable caractère de nouvelle ligne. Un moment où printf obtient \n à interpréter est avec %s\\\\n . C'est parce que votre Shell actuel change \\\\n dans une chaîne de caractères entre guillemets à \\n , sed interprète \\ como \ la Shell intérieure est citée une seule fois. \n et seulement alors printf obtient \n (plus de niveaux que dans cette question mais similaire). Ayant \n interprété par sed ne rompt pas la commande, vous pouvez donc vous en tenir à cette forme simple.

Ce serait bien si vous pouviez laisser le abc= pièce intacte et ne remplacer que 123 . Pour cela, une fonction "lookbehind" est utile, mais sed ne le supporte pas . Une des réponses à la question liée conseille un groupe de capture et une rétro-référence dans la chaîne de remplacement, c'est exactement ce que nous avons fait avec \1 .


Un gros problème avec l'exécution d'une commande Shell de cette façon est qu'il y a une vulnérabilité d'injection de code. Dans la Shell-commande à venir, nous avons mis entre guillemets \1 y \2 . Si sed remplace l'un d'entre eux par une chaîne de caractères contenant ' le Shell intérieur fermera la citation prématurément. Essayez la commande suivante (le sed est la même que ci-dessus) :

echo "abc=123'; rm -i /very/important/file'" \
 | sed "s/\([^=]*\)=\(.*\)/printf '%s' '\1='; printf '%s\n' '\2 * 2' | bc/e"

(J'ai utilisé rm -i au cas où vous auriez vraiment /very/important/file dans votre système).

Vous devez être sûr que l'entrée n'injectera pas de code. Ou vous devez réécrire la regex ( [^=]* y .* pièces) donc potentiellement dangereux ' ne peut pas obtenir d'entrée arbitraire à la commande Shell. Notez que si nous avons cité l'ensemble de la sed et l'expression entre guillemets \1 y \2 les doubles guillemets atteindraient le Shell interne et alors " (pas ' ) dans l'entrée serait dangereux, ainsi que $var , $(code) et autres ! Et si on laissait \1 o \2 non citée dans le contexte de la Shell intérieure, ce serait encore pire.

En général, il faut passer des données arbitraires à un Shell comme arguments de ligne de commande, et non dans une chaîne de commande. Par exemple, avec find -exec on peut faire ça bien . L'injection de code est alors impossible, à moins que la commande réelle (statique) Shell n'exécute un paramètre positionnel ou ne manipule mal quelque chose. Malheureusement, en invoquant une Shell depuis GNU sed tout ce que vous pouvez passer est une chaîne de commande.

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