$(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.