Je veux pouvoir capturer la sortie exacte d'une substitution de commande, y compris les nouveaux caractères de ligne de fin .
Je me rends compte qu'ils sont supprimés par défaut, donc une manipulation peut être nécessaire pour les conserver, et je veux conserver le code de sortie d'origine .
Par exemple, étant donné une commande avec un nombre variable de sauts de ligne de fin et de code de sortie:
f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f
Je veux exécuter quelque chose comme:
exact_output f
Et que la sortie soit:
Output: $'\n\n'
Exit: 5
Je m'intéresse aux deux bash
et POSIX sh
.
$IFS
, donc elle ne sera pas capturée comme argument.IFS
(essayer( IFS=:; subst=$(printf 'x\n\n\n'); printf '%s' "$subst" )
seulement les nouvelles lignes dépouillées se..\t
Et `` ne sont pas, etIFS
ne l' affecte pas.tcsh
Réponses:
Coques POSIX
L' astuce habituelle ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) pour obtenir la sortie complète d'une commande est la suivante:
L'idée est d'ajouter et d'extra
.\n
. La substitution de commande ne fera que supprimer cela\n
. Et vous vous déshabillez.
avec${output%.}
.Notez que dans les shells autres que
zsh
, cela ne fonctionnera toujours pas si la sortie a des octets NUL. Avecyash
, cela ne fonctionnera pas si la sortie n'est pas du texte.Notez également que dans certains paramètres régionaux, le caractère que vous utilisez pour insérer à la fin est important.
.
devrait généralement être bien, mais certains autres pourraient ne pas. Par exemplex
(comme utilisé dans certaines autres réponses) ou@
ne fonctionnerait pas dans un environnement local à l'aide des jeux de caractères BIG5, GB18030 ou BIG5HKSCS. Dans ces jeux de caractères, le codage d'un certain nombre de caractères se termine dans le même octet que le codage dex
ou@
(0x78, 0x40)Par exemple,
ū
dans BIG5HKSCS est 0x88 0x78 (etx
est 0x78 comme en ASCII, tous les jeux de caractères sur un système doivent avoir le même codage pour tous les caractères du jeu de caractères portable qui comprend les lettres anglaises,@
et.
). Donc , sicmd
étaitprintf '\x88'
et nous inséronsx
après,${output%x}
ne parviendrait pas à dépouiller quex
comme$output
contiendront en faitū
.L'utilisation à la
.
place pourrait entraîner le même problème en théorie s'il y avait des caractères dont l'encodage se termine par le même encodage que.
, mais pour avoir vérifié il y a quelque temps, je peux dire qu'aucun des jeux de caractères pouvant être disponibles pour une utilisation dans un environnement local dans les systèmes Debian, FreeBSD ou Solaris ont de tels caractères, ce qui est assez bon pour moi (et pourquoi je me suis installé sur.
lequel est également le symbole pour marquer la fin d'une phrase en anglais, cela semble donc approprié).Une approche plus correcte comme discuté par @Arrow serait de changer les paramètres régionaux en C uniquement pour la suppression du dernier caractère (
${output%.}
), ce qui garantirait qu'un seul octet est supprimé, mais cela compliquerait considérablement le code et pourrait potentiellement introduire des problèmes de compatibilité de sa propre.alternatives bash / zsh
Avec
bash
etzsh
, en supposant que la sortie n'a pas de NUL, vous pouvez également faire:Pour obtenir le statut de sortie de
cmd
, vous pouvez le fairewait "$!"; ret=$?
enbash
mais pas enzsh
.rc / es / akanaga
Pour être complet, notez que
rc
/es
/akanga
a un opérateur pour cela. Dans ceux-ci, la substitution de commandes, exprimée sous la forme`cmd
(ou`{cmd}
pour des commandes plus complexes) renvoie une liste (en la divisant$ifs
, espace-tab-newline par défaut). Dans ces coquilles (par opposition aux coquilles de type Bourne), le dépouillement de la nouvelle ligne n'est effectué que dans le cadre de ce$ifs
fractionnement. Vous pouvez donc soit vider,$ifs
soit utiliser le``(seps){cmd}
formulaire où vous spécifiez les séparateurs:ou:
Dans tous les cas, l'état de sortie de la commande est perdu. Vous auriez besoin de l'intégrer dans la sortie et de l'extraire ensuite, ce qui deviendrait laid.
poisson
Dans le poisson, la substitution de commande est avec
(cmd)
et n'implique pas un sous-shell.Crée un
$var
tableau avec toutes les lignes dans la sortie decmd
if$IFS
n'est pas vide, ou avec la sortie decmd
dépouillé jusqu'à un (contrairement à tous dans la plupart des autres shells) caractère de nouvelle ligne si$IFS
est vide.Donc, il y a toujours un problème dans cela
(printf 'a\nb')
et(printf 'a\nb\n')
étendre à la même chose même avec un vide$IFS
.Pour contourner cela, le mieux que j'ai pu trouver était:
Une alternative est de faire:
Coquille de Bourne
Le shell Bourne ne supportait
$(...)
ni le formulaire, ni l'${var%pattern}
opérateur, il peut donc être assez difficile d'y arriver. Une approche consiste à utiliser eval et à citer:Ici, nous générons un
à transmettre à
eval
. En ce qui concerne l'approche POSIX, s'il'
s'agissait de l'un de ces caractères dont l'encodage peut être trouvé à la fin des autres caractères, nous aurions un problème (bien pire car il deviendrait une vulnérabilité d'injection de commande), mais heureusement, comme.
, ce n'est pas l'un d'entre eux, et cette technique de citation est généralement celle qui est utilisée par tout ce qui cite le code shell (notez que cela\
a le problème, donc ne devrait pas être utilisé (exclut également à l'"..."
intérieur de laquelle vous devez utiliser des barres obliques inverses pour certains caractères) Ici, nous ne l'utilisons qu'après un'
qui est OK).tcsh
Voir tcsh préserver les nouvelles lignes dans la substitution de commande `... '
(sans prendre soin du statut de sortie, que vous pourriez résoudre en l'enregistrant dans un fichier temporaire (
echo $status > $tempfile:q
après la commande))la source
zsh
peut stockerNUL
dans une variable, pourquoi neIFS= read -rd '' output < <(cmd)
fonctionnerait pas ? Il doit être capable de stocker la longueur d'une chaîne ... l'encode-t-il''
comme une chaîne de 1 octet\0
plutôt que comme une chaîne de 0 octet?read -d ''
est traité commeread -d $'\0'
(bash
aussi bien qu'il y en$'\0'
ait comme''
partout).x
si c'est ce qui a été ajouté. Veuillez jeter un œil à ma réponse modifiée.var=value command eval
astuce a été discutée ici ( aussi ) et sur la liste de diffusion austin-group avant. Vous constaterez qu'il n'est pas portable (et il est assez évident lorsque vous essayez des choses commea=1 command eval 'unset a; a=2'
ou pire qu'il n'était pas destiné à être utilisé comme ça). De même pour lesavedVAR=$VAR;...;VAR=$savedVAR
qui ne fait pas ce que vous vouliez quand il$VAR
était initialement non réglé. Si c'est pour contourner un problème théorique uniquement (un bug qui ne peut pas être atteint en pratique), OMI, ça ne vaut pas la peine. Pourtant, je vais vous soutenir pour avoir essayé.LANG=C
pour supprimer un octet d'une chaîne? Vous soulevez des préoccupations autour du vrai point, toutes sont faciles à résoudre. (1) aucun désarmement n'est utilisé (2) Testez la variable avant de la modifier. @ StéphaneChazelasPour la nouvelle question, ce script fonctionne:
À l'exécution:
La description plus longue
La sagesse habituelle pour les coques POSIX pour gérer la suppression de
\n
:Cela est nécessaire car les dernières nouvelles lignes ( S ) sont supprimées par l'extension de commande selon la spécification POSIX :
À propos d'une fuite
x
.Il a été dit dans cette question qu'un
x
pourrait être confondu avec l'octet de fin d'un caractère dans un codage. Mais comment allons-nous deviner quel ou quel caractère est meilleur dans une langue dans un codage possible, c'est une proposition difficile, pour dire le moins.Pourtant; C'est tout simplement incorrect .
La seule règle que nous devons suivre est d'ajouter exactement ce que nous supprimons.
Il devrait être facile de comprendre que si nous ajoutons quelque chose à une chaîne existante (ou une séquence d'octets) et que nous supprimons plus tard exactement le même quelque chose, la chaîne d'origine (ou la séquence d'octets) doit être la même.
Où allons-nous mal? Quand on mélange des caractères et des octets .
Si nous ajoutons un octet, nous devons supprimer un octet, si nous ajoutons un caractère, nous devons supprimer exactement le même caractère .
La deuxième option, l'ajout d'un caractère (et la suppression ultérieure du même caractère exact) peut devenir compliquée et complexe, et, oui, les pages de code et les encodages peuvent gêner.
Cependant, la première option est tout à fait possible et, après l'avoir expliquée, elle deviendra simple.
Ajoutons un octet, un octet ASCII (<127), et pour garder les choses aussi peu alambiquées que possible, disons un caractère ASCII dans la plage de az. Ou comme nous devrions le dire, un octet dans la plage hexadécimale
0x61
-0x7a
. Permet de choisir l'un de ceux-ci, peut-être un x (vraiment un octet de valeur0x78
). Nous pouvons ajouter un tel octet avec en concaténant un x à une chaîne (supposons uné
):Si nous regardons la chaîne comme une séquence d'octets, nous voyons:
Une séquence de chaînes qui se termine par un x.
Si nous supprimons ce x (valeur d'octet
0x78
), nous obtenons:Cela fonctionne sans problème.
Un exemple un peu plus difficile.
Disons que la chaîne qui nous intéresse se termine en octets
0xc3
:Et permet d'ajouter un octet de valeur
0xa9
La chaîne est devenue ceci maintenant:
Exactement ce que je voulais, les deux derniers octets sont un caractère dans utf8 (donc n'importe qui pourrait reproduire ces résultats dans sa console utf8).
Si nous supprimons un caractère, la chaîne d'origine sera modifiée. Mais ce n'est pas ce que nous avons ajouté, nous avons ajouté une valeur d'octet, qui se trouve être écrite comme un x, mais un octet de toute façon.
Ce dont nous avons besoin pour éviter de mal interpréter les octets en tant que caractères. Ce dont nous avons besoin, c'est d'une action qui supprime l'octet que nous avons utilisé
0xa9
. En fait, ash, bash, lksh et mksh semblent tous faire exactement cela:Mais pas ksh ou zsh.
Cependant, c'est très facile à résoudre, disons à tous ces shells de supprimer les octets:
c'est tout, tous les shells testés fonctionnent (sauf yash) (pour la dernière partie de la chaîne):
Aussi simple que cela, dites au shell de supprimer un caractère LC_ALL = C, qui est exactement un octet pour toutes les valeurs d'octets de
0x00
à0xff
.Solution pour commentaires:
Pour l'exemple discuté dans les commentaires, une solution possible (qui échoue dans zsh) est:
Cela supprimera le problème d'encodage.
la source
zsh
ajoutéprintf -v
pour compatibilité avecbash
en décembre 2015${var%?}
toujours un octet est toujours plus correcte en théorie, mais: 1-LC_ALL
etLC_CTYPE
remplacer$LANG
, vous devez donc définirLC_ALL=C
2- vous ne pouvez pas faire levar=${var%?}
dans un sous-shell comme le ferait le changement être perdu, vous devez donc enregistrer et restaurer la valeur et l'état deLC_ALL
(ou recourir à deslocal
fonctionnalités de portée non POSIX ) 3- la modification des paramètres régionaux à mi-chemin dans le script n'est pas entièrement prise en charge dans certains shells comme yash. D'un autre côté, dans la pratique, il.
n'y a jamais de problème dans les jeux de caractères réels, donc son utilisation évite de se mélanger avec LC_ALL.Vous pouvez sortir un caractère après la sortie normale puis le supprimer:
Il s'agit d'une solution compatible POSIX.
la source