Voici le problème: J’ai ce script foo.py
, et si l’utilisateur l’invoque sans l’option --bar
, j’aimerais afficher le message d’erreur suivant:
Please add the --bar option to your command, like so: python foo.py --bar
Maintenant, la partie délicate est que l’utilisateur peut invoquer la commande de plusieurs façons:
python foo.py
comme dans l’exemple /usr/bin/foo.py
frob='python foo.py'
, et ont en fait lancé frob
flab=!/usr/bin/foo.py
, et ils ont utilisé git flab
Dans tous les cas, je souhaiterais que le message reflète la manière dont l’utilisateur a invoqué la commande, de sorte que l’exemple que je propose aurait du sens.
sys.argv
contient toujours foo.py
et /proc/$$/cmdline
ne connaît pas les alias. Il me semble que la seule source possible pour cette information serait bash lui-même, mais je ne sais pas comment le demander.
Des idées?
MISE À JOUR Que diriez-vous si nous limitons les scénarios possibles à ceux énumérés ci-dessus?
MISE À JOUR 2 : Beaucoup de personnes ont écrit de très bonnes explications sur la raison pour laquelle cela n’est pas possible dans le cas général, alors je voudrais limiter ma question à ceci:
Sous les hypothèses suivantes:
foo
où foo est un lien symbolique / usr / bin / foo -> foo.py git foo
où alias.foo =! / usr / bin / foo dans ~/.gitconfig
git baz
où alias.baz =! / usr / bin / foo dans ~/.gitconfig
Existe-t-il un moyen de distinguer entre 1 et (2,3) du script? Existe-t-il un moyen de distinguer 2 et 3 du script?
Je sais que c’est un long coup, alors j’accepte la réponse de Charles Duffy pour le moment.
MISE À JOUR 3 : Jusqu’à présent, le point le plus prometteur a été suggéré par Charles Duffy dans les commentaires ci-dessous. Si je peux obtenir mes utilisateurs d’avoir
trap 'export LAST_BASH_COMMAND=$(history 1)' DEBUG
dans leur .bashrc
, alors je peux utiliser quelque chose comme ça dans mon code:
like_so = None cmd = os.environ['LAST_BASH_COMMAND'] if cmd is not None: cmd = cmd[8:] # Remove the history counter if cmd.startswith("foo "): like_so = "foo --bar " + cmd[4:] elif cmd.startswith(r"git foo "): like_so = "git foo --bar " + cmd[8:] elif cmd.startswith(r"git baz "): like_so = "git baz --bar " + cmd[8:] if like_so is not None: print("Please add the --bar option to your command, like so:") print(" " + like_so) else: print("Please add the --bar option to your command.")
De cette façon, je montre le message général si je ne parviens pas à obtenir leur méthode d’invocation. Bien sûr, si je compte sur la modification de l’environnement de mes utilisateurs, je pourrais tout aussi bien m’assurer que les différents alias exportent leurs propres variables d’environnement, mais au moins, cette technique me permet d’utiliser la même technique autre script que je pourrais append plus tard.
Le démarrage d’un programme sous UNIX se fait comme suit au niveau de l’appel système sous-jacent:
int execve(const char *path, char *const argv[], char *const envp[]);
Notamment, il y a trois arguments:
argv[0]
ou $0
– est transmis à cet exécutable pour refléter le nom sous lequel il a été démarré) Nulle part ici, il n’y a une chaîne qui fournit la commande shell originale saisie par l’utilisateur à partir de laquelle l’appel du nouveau processus a été demandé. Ceci est particulièrement vrai car tous les programmes ne sont pas du tout lancés depuis un shell ; Considérez le cas où votre programme est démarré à partir d’un autre script Python avec shell=False
.
argv[0]
; Cela fonctionne pour les liens symboliques. Vous pouvez même voir les outils UNIX standard en procédant comme suit:
$ ls '*.txt' # sample command to generate an error message; note "ls:" at the front ls: *.txt: No such file or directory $ (exec -a foobar ls '*.txt') # again, but tell it that its name is "foobar" foobar: *.txt: No such file or directory $ alias somesuch=ls # this **doesn't** happen with an alias $ somesuch '*.txt' # ...the program still sees its real name, not the alias! ls: *.txt: No such file
pipes.quote()
(Python 2) ou shlex.quote()
(Python 3) pour le faire en toute sécurité. try: from pipes import quote # Python 2 except ImportError: from shlex import quote # Python 3 cmd = ' '.join(quote(s) for s in open('/proc/self/cmdline', 'r').read().split('\0')[:-1]) print("We were called as: {}".format(cmd))
Encore une fois, cela ne “dé-développez” pas les alias, revenez au code qui a été appelé pour appeler une fonction qui a invoqué votre commande, etc. il n’y a pas de sonnerie qui ne sonne pas.
def find_cmdline(pid): return open('/proc/%d/cmdline' % (pid,), 'r').read().split('\0')[:-1] def find_ppid(pid): stat_data = open('/proc/%d/stat' % (pid,), 'r').read() stat_data_sanitized = re.sub('[(]([^)]+)[)]', '_', stat_data) return int(stat_data_sanitized.split(' ')[3]) def all_parent_cmdlines(pid): while pid > 0: yield find_cmdline(pid) pid = find_ppid(pid) def find_git_parent(pid): for cmdline in all_parent_cmdlines(pid): if cmdline[0] == 'git': return ' '.join(quote(s) for s in cmdline) return None
Voir la note en bas concernant le script wrapper initialement proposé.
Une nouvelle approche plus flexible consiste pour le script python à fournir une nouvelle option de ligne de commande, permettant aux utilisateurs de spécifier une chaîne personnalisée qu’ils préféreraient voir dans les messages d’erreur.
Par exemple, si un utilisateur préfère appeler le script python ‘ myPyScript.p
y’ via un alias, il peut modifier la définition d’alias à partir de ceci:
alias myAlias='myPyScript.py $@'
pour ça:
alias myAlias='myPyScript.py --caller=myAlias $@'
S’ils préfèrent appeler le script python à partir d’un script shell, il peut utiliser l’option de ligne de commande supplémentaire comme suit:
#!/bin/bash exec myPyScript.py "$@" --caller=${0##*/}
Autres applications possibles de cette approche:
bash -c myPyScript.py --caller="bash -c myPyScript.py" myPyScript.py --caller=myPyScript.py
Pour répertorier les lignes de commande étendues, voici un script ” pyTest.py
“, basé sur les commentaires de @CharlesDuffy, qui répertorie cmdline pour le script python en cours d’exécution, ainsi que le processus parent qui l’a généré. Si le nouvel argument -caller est utilisé, il apparaîtra dans la ligne de commande, bien que les alias aient été développés, etc.
#!/usr/bin/env python import os, re with open ("/proc/self/stat", "r") as myfile: data = [x.ssortingp() for x in str.split(myfile.readlines()[0],' ')] pid = data[0] ppid = data[3] def commandLine(pid): with open ("/proc/"+pid+"/cmdline", "r") as myfile: return [x.ssortingp() for x in str.split(myfile.readlines()[0],'\x00')][0:-1] pid_cmdline = commandLine(pid) ppid_cmdline = commandLine(ppid) print "%r" % pid_cmdline print "%r" % ppid_cmdline
Après avoir enregistré ceci dans un fichier nommé ” pytest.py
“, puis appelé depuis un script bash appelé ” pytest.sh
” avec divers arguments, voici la sortie:
$ ./pytest.sh ab "cd" e ['python', './pytest.py'] ['/bin/bash', './pytest.sh', 'a', 'b', 'c d', 'e']
REMARQUE: les critiques du script d’emballage original aliasTest.sh
étaient valides. Bien que l’existence d’un alias prédéfini fasse partie de la spécification de la question et puisse être présumée exister dans l’environnement utilisateur, la proposition a défini l’alias (créant une impression trompeuse selon laquelle elle faisait partie de la recommandation plutôt qu’un partie de l’environnement de l’utilisateur), et il n’a pas montré comment le wrapper communiquerait avec le script appelé python. En pratique, l’utilisateur devrait soit générer le wrapper, soit définir l’alias dans l’encapsuleur, et le script python devrait déléguer l’impression des messages d’erreur à plusieurs scripts d’appel personnalisés (où résidaient les informations appelantes) et les clients auraient dû: appeler les scripts wrapper. La résolution de ces problèmes a conduit à une approche plus simple, qui peut être étendue à un nombre illimité de cas d’utilisation supplémentaires.
Voici une version moins confuse du script original, à titre de référence:
#!/bin/bash shopt -s expand_aliases alias myAlias='myPyScript.py' # called like this: set -o history myAlias $@ _EXITCODE=$? CALL_HISTORY=( `history` ) _CALLING_MODE=${CALL_HISTORY[1]} case "$_EXITCODE" in 0) # no error message required ;; 1) echo "customized error message #1 [$_CALLING_MODE]" 1>&2 ;; 2) echo "customized error message #2 [$_CALLING_MODE]" 1>&2 ;; esac
Voici la sortie:
$ aliasTest.sh 1 2 3 ['./myPyScript.py', '1', '2', '3'] customized error message #2 [myAlias]
Il n’y a aucun moyen de distinguer quand un interpréteur pour un script est explicitement spécifié sur la ligne de commande et quand il est déduit par le système d’exploitation de la ligne de hachage.
Preuve:
$ cat test.sh #!/usr/bin/env bash ps -o command $$ $ bash ./test.sh COMMAND bash ./test.sh $ ./test.sh COMMAND bash ./test.sh
Cela vous empêche de détecter la différence entre les deux premiers cas de votre liste.
Je suis également convaincu qu’il n’ya pas de moyen raisonnable d’identifier les autres moyens (médiatisés) d’appeler un commandement.
Je peux voir deux façons de le faire:
argparse
peut être utilisé pour le faire de manière fiable. Cela ne fonctionne que si vous pouvez modifier ce script. python
sur votre système. La première option étant bien documentée, voici un peu plus de détails sur la seconde:
Quelle que soit la manière dont votre script est appelé, python
est exécuté. Le but ici est de remplacer l’exécutable python
par un script qui vérifie si foo.py
fait partie des arguments, et si c’est le cas, vérifiez si --bar
est également. Sinon, imprimez le message et renvoyez-le.
Dans tous les autres cas, exécutez le véritable exécutable python.
Maintenant, si tout va bien, l’exécution de python se fait via le shebang suivant: #!/usr/bin/env python3
, ou python foo.py
, plutôt qu’une variante de #!/usr/bin/python
ou /usr/bin/python foo.py
De cette façon, vous pouvez modifier la variable $PATH
et append un répertoire dans lequel réside votre faux python
.
Dans l’autre cas, vous pouvez remplacer l’ /usr/bin/python executable
, au risque de ne pas jouer bien avec les mises à jour.
Une manière plus complexe de le faire serait probablement avec les espaces de noms et les assemblys, mais ce qui précède est probablement suffisant, surtout si vous avez des droits d’administrateur.
Exemple de ce qui pourrait fonctionner comme un script:
#!/usr/bin/env bash function checkbar { for i in "$@" do if [ "$i" = "--bar" ] then echo "Well done, you added --bar!" return 0 fi done return 1 } command=$(basename ${1:-none}) if [ $command = "foo.py" ] then if ! checkbar "$@" then echo "Please add --bar to the command line, like so:" printf "%q " $0 printf "%q " "$@" printf -- "--bar\n" exit 1 fi fi /path/to/real/python "$@"
Cependant, après relecture de votre question, il est évident que je l’ai mal comprise. À mon avis, il est juste d’imprimer soit “foo.py doit être appelé comme foo.py –bar”, “s’il vous plaît append des barres à vos arguments” ou “s’il vous plaît essayez (au lieu de)”, indépendamment de ce que le utilisateur entré:
--bar
/usr/bin/foo.py
ou python foo.py
:
Je sais que c’est la tâche bash
, mais je pense que la manière la plus simple est de modifier ‘foo.py’. Bien sûr, cela dépend du niveau de script compliqué, mais peut-être que ça ira. Voici un exemple de code:
#!/usr/bin/python import sys if len(sys.argv) > 1 and sys.argv[1] == '--bar': print 'make magic' else: print 'Please add the --bar option to your command, like so:' print ' python foo.py --bar'
Dans ce cas, la manière dont l’utilisateur exécute ce code importe peu.
$ ./a.py Please add the --bar option to your command, like so: python foo.py --bar $ ./a.py -dua Please add the --bar option to your command, like so: python foo.py --bar $ ./a.py --bar make magic $ python a.py --t Please add the --bar option to your command, like so: python foo.py --bar $ /home/3sky/test/a.py Please add the --bar option to your command, like so: python foo.py --bar $ alias a='python a.py' $ a Please add the --bar option to your command, like so: python foo.py --bar $ a --bar make magic