En utilisant uniquement les fonctionnalités du shell POSIX, existe-t-il une "commande simple" qui ne fait rien et ne change pas la valeur de $?
Les gens décrivent généralement :
comme une commande no-op pour le shell, mais cela définit toujours $?
à zéro, donc ce n'est pas ce que je veux.
Ceci est nécessaire à un programme qui génère des scripts shell. À plusieurs endroits, il doit émettre un bloc if-then-else
if CONDITION then TRUE-COMMANDS else FALSE-COMMANDS fi
mais, en raison de complications supplémentaires que je préfère ne pas essayer d'expliquer maintenant, il ne peut pas dire de manière fiable si TRUE-COMMANDS
et FALSE-COMMANDS
sont vides. Une clause then- or else vide sera une erreur de syntaxe shell.
:
Peut être mis au début de then
-clause pour prendre soin de TRUE-COMMANDS
étant vide, mais cela ne fonctionnera pas pour FALSE-COMMANDS
parce qu'il aplatit $?
et les FALSE-COMMANDS
pourraient vouloir regarder la valeur $?
a été défini par la condition. Pour la même raison, :
ne peut pas être mis après FALSE-COMMANDS
â € » le code après if-then-else besoin de puissance pour voir $?
à partir de la dernière opération dans le if-then-else.
Des points bonus si vous pouvez éviter:
Forking: (exit $?)
Fait le travail, mais il y a tellement de ces blocs conditionnels dans un script généré que cela produit un ralentissement mesurable.
Fonctions: donné nop () { return $? }
alors nop
fait le travail, mais en raison de plus de complications que je préfère ne pas entrer, il n'est pas pratique de définir nop
suffisamment tôt pour tous les endroits qui en auraient besoin.
3 Réponses :
Il n'est certainement pas possible dans un shell compatible POSIX d'écrire une commande qui ne touche pas $?
. En lisant la norme IEEE 1003.1-2017 vol. 3 chapitre 2 «Shell Command Language», nous apprenons que:
$?
«[e] xpands à l'état de sortie décimal du pipeline le plus récent (voir Section 2.9.2)».!
, avec des commandes jointes par |
. À propos de l'état de sortie, il ditSi le pipeline ne commence pas par le
!
réservé, l'état de sortie doit être l'état de sortie de la dernière commande spécifiée dans le pipeline. Sinon, l'état de sortie doit être le NON logique de l'état de sortie de la dernière commande.
S'il n'y a pas de nom de commande, mais que la commande contenait une substitution de commande, la commande doit se terminer avec l'état de sortie de la dernière substitution de commande effectuée. Sinon, la commande doit se terminer avec un état de sortie nul.
&&
ou ||
}, chacun terminé par ;
ou &
(avec un final ;
facultatif)}, joint par des caractères de nouvelle ligne. Une liste composée renvoie l'état de sortie du dernier pipeline exécuté de manière synchrone; Les pipelines exécutés de manière asynchrone (ceux terminés par &
) définissent l'état de sortie sur zéro. Aucune des séquences ne peut être vide, ce qui garantit qu'au moins un pipeline sera exécuté.case
, §2.9.4.3 ou if
, §2.9.4.4) ou une boucle ( for
, §2.9.4.2; while
, §2.9.4.5; until
, §2.9.4.6). Si le corps de cette construction n'est pas exécuté, elle renvoie un statut de sortie nul.if
comme étant une liste composée, décrite par le §2.9.3 mentionné ci-dessus. Cela signifie que le corps d'une construction if
contiendra toujours au moins une commande qui écrasera $?
. La spécification ne laisse même aucune marge de manœuvre pour le «comportement défini par la mise en œuvre». Tout ce qui précède signifie que la seule façon d'écrire une commande no-op qui préserve $?
est en lisant la valeur en $?
et le renvoyer. Il y a trois constructions dans le shell POSIX qui en sont capables, dont deux sont déjà mentionnées dans le corps de la question: une exit
d'un sous-shell ou une invocation de fonction.
Le troisième est .
: la déclaration d'approvisionnement en shell mentionnée dans la réponse de @ Jens. En recherchant un script contenant juste un return "$?"
commande, la valeur de $?
peuvent être préservés. Cependant, cela vous oblige à faire en sorte qu'un script approprié soit trouvé à un emplacement connu, ce qui, je suppose, est tout aussi gênant que de s'assurer qu'une fonction no-op a été définie suffisamment tôt dans le fichier (sinon plus).
Si vous pliez un peu l'exigence stricte de POSIX, même cela peut être surmonté:
. /dev/stdin <<EOF return "$?" EOF
/dev/stdin
n'est pas une fonctionnalité POSIX, mais est largement disponible; il est explicitement répertorié dans IEEE Std 1003.1-2017 vol. 1 §2.1.1 comme extension. L'extrait ci-dessus a été testé pour fonctionner dans bash 5.0.8, dash 0.5.11 et busybox sh 1.30.1 sous Linux (avec une configuration /dev
appropriée et ainsi de suite).
Après réflexion, je vous donne la prime parce que j'apprécie votre fouille dans POSIX pour confirmer que je n'avais manqué aucune possibilité, mais j'accepte la réponse de kvantour qui m'a quand même donné une nouvelle possibilité.
Le plus simple serait d'utiliser une simple mission. Au lieu d'utiliser :
, do _rc=$?
.
if condition; then [ list-true ] # optional _rc=$? else [ list-false ] # optional _rc=$? fi ( exit $_rc ) # optional list-post-if
En utilisant cette variable _rc
, vous avez stocké l'état de sortie de la dernière commande exécutée, qu'il s'agisse d'une condition
ou de la dernière commande dans list-true
ou list-false
.
list-post-if
pour utiliser _rc
au lieu de $?
.(exit $_rc)
juste après l'instruction conditionnelle. Ceci, cependant, nécessite un sous-shell, mais ce n'est qu'un seul.Après réflexion, j'ai donné à user3840170 la prime parce que j'apprécie leur fouille dans POSIX pour confirmer que je n'avais manqué aucune possibilité, mais j'accepte cette réponse qui m'a quand même donné une nouvelle possibilité.
@zwol Merci, très apprécié et bonne décision de donner à user3840170 la prime.
Ne laissez personne vous dire qu'il n'y a que des fonctions et des sous-shells.
Pouvez-vous créer ou distribuer un autre petit fichier? Si tel est le cas, vous pouvez créer un fichier avec juste
cat > keepstatus.c <<EOF #include <stdlib.h> int main(int argc, char **argv) { return argv[1] ? atoi(argv[1]) : 0; } EOF $CC -o keepstatus keepstatus.c
puis source-le comme une commande "vide" gardant l'état de sortie:
$ echo 'return $?' > keepstatus $ ls foobar ls: fooobar: No such file or directory $ . ./keepstatus $ echo $? 2 $ sleep 100 ^C $ . ./keepstatus $ echo $? 130
Ne bifche pas, n'utilise pas de fonctions, pas de variable supplémentaire, garde le statut et est aussi POSIXly que possible.
Il y a même une quatrième façon dont je peux voir, lorsque je sacrifie les points bonus pour avoir bifurqué et supposé, puisque vous êtes dans le territoire autoconf m4, que trouver et utiliser un compilateur hôte est un jeu d'enfant.
return $?
Ensuite, utilisez /path/to/keepstatus $?
.
Je pense que cela fonctionne bien, mais la lecture d'un fichier externe ne créerait-elle pas plus de frais généraux que d'appeler un sous-shell? J'ai essayé quelque chose de similaire en utilisant un heredoc, mais cela ne fonctionne pas du tout. Néanmoins, vote favorable.
@kvantour Je dirais que c'est moins de frais généraux, puisque les sous-shells doivent faire une certaine comptabilité, tandis que l'approvisionnement est ouvert / lu / exécuté / fermé. Mais je n'ai jamais implémenté de shell donc tout cela pourrait être de la fantaisie :-)
Faire en sorte qu'un fichier séparé existe dans un emplacement connu est probablement encore plus problématique que de s'assurer qu'une fonction est définie suffisamment tôt dans le même script, ce que le demandeur a mentionné comme n'étant pas particulièrement faisable. Ce n'est donc pas vraiment une solution.
@ user3840170 Comment le savez-vous? L'utilisateur crée du code shell. Un seul return $?
est exactement un tout petit peu de cela. Oh, et j'attends toujours une réponse pourquoi vous avez volé ma solution et l'avez plagiée dans votre réponse. Qu'est devenu le crédit là où le crédit est dû?
Je ne le fais pas - mais le demandeur voulait une solution dans le shell POSIX, donc l'hypothèse du pire des cas est qu'il devrait pouvoir fonctionner tel quel sur un système POSIX arbitraire, où de telles choses n'ont pas été pré-arrangées, surtout depuis il a été mentionné que la définition d'une fonction de non-opération suffisamment tôt ne serait pas particulièrement pratique. Je suppose donc que pré-organiser d'autres choses serait tout aussi gênant.
Le moyen le plus simple pour le script généré de s'assurer qu'un tel return $?
one-liner est disponible serait de le créer dans /tmp
tôt dans le script généré lui-même - mais alors, vous pourriez aussi bien y mettre une définition d'une fonction no-op et vous passer complètement du script one-liner.
De plus, vous avez votre crédit, que voulez-vous de plus? J'ai également ajouté le bit /dev/stdin
, donc ce n'est pas comme si je n'y avais rien ajouté.
@ user3840170 Je trouve votre attitude à deviner quelle est une solution assez grossière. C'est entièrement à zwol de décider. Ma solution répond à toutes ses exigences et ne viole aucun de ses no-gos. J'ai même dû vous corriger sur le POSIXness de .
(point).
Il serait un peu plus facile pour moi de préorganiser l'existence de ce script d'aide que de préorganiser une fonction shell. Cependant, je devrais également faire en sorte que l'assistant soit nettoyé par la suite, et le piège 0 est déjà utilisé pour quelque chose sans rapport, donc dans l'ensemble, cette approche n'est pas plus facile que d'utiliser une fonction.
Je pense que CONDITION fixera $ ?; vous ne pouvez donc pas évaluer votre
if
sans le définir.@mevets Oui, c'est exactement le problème.
status=$?
au début deFALSE-COMMANDS
faut définir l'status
sur l'état de sortie deCONDITION
, pas sur zéro. Si je mets un:
immédiatement aprèselse
cela n'arrivera pas.sinon vous pourriez mettre un
while CONDITION; do : ; done
comme votre instruction nulle pour la clause else, puisque CONDITION ne peut pas être vraie, elle ne s'exécuterait jamais.@mevets
CONDITION
peut avoir des effets secondaires, donc ce n'est pas bon.function r() { return $1; }
alorsx=$?; if CONDITION; then r $x; else ls; fi
Comment ne pas savoir si
FALSE-COMMANDS
est vide si c'est vous qui le générez ? Cela ressemble à un problème XY; vous devriez demander comment savoir si la liste de commandes est vide ou non.@chepner Lisez l'intégralité de git.savannah.gnu.org/cgit/autoconf.git/tree/lib/m4sugar/… et ... / m4sh.m4, en accordant une attention particulière aux commentaires ci-dessus AS_IF, m4_expand et m4_provide, et alors si vous pouvez penser à quelque chose, je suis toutes les oreilles.
Veuillez lire stackoverflow.com/help/minimal-reproducible-example .
@chepner La question que j'ai posée peut répondre telle quelle. Je ne vais pas faire plus d'efforts dans une approche alternative que j'avais déjà décidé n'était pas faisable avant de poser la question.
Il n'y a aucune commande qui ne modifie pas
$?
.