web-dev-qa-db-fra.com

Capturer automatiquement la sortie de la dernière commande dans une variable à l'aide de Bash?

J'aimerais pouvoir utiliser le résultat de la dernière commande exécutée dans une commande ultérieure. Par exemple,

$ find . -name foo.txt
./home/user/some/directory/foo.txt

Maintenant, disons que je veux pouvoir ouvrir le fichier dans un éditeur, le supprimer ou faire autre chose, par exemple.

mv <some-variable-that-contains-the-result> /some/new/location

Comment puis-je le faire? Peut-être en utilisant une variable bash?

Mise à jour:

Pour clarifier, je ne veux pas assigner des choses manuellement. Ce que je recherche, c’est quelque chose comme les variables bash intégrées, par exemple.

ls /tmp
cd $_

$_ contient le dernier argument de la commande précédente. Je veux quelque chose de similaire, mais avec la sortie de la dernière commande.

Mise à jour finale:

La réponse de Seth a très bien fonctionné. Quelques éléments à garder à l'esprit:

  • n'oubliez pas de touch /tmp/x en essayant la solution pour la toute première fois
  • le résultat ne sera enregistré que si le code de sortie de la dernière commande a réussi
134
armandino

C’est une solution vraiment déconcertante, mais elle semble fonctionner la plupart du temps de temps en temps. Lors des tests, j’ai remarqué que cela ne fonctionnait parfois pas très bien avec un ^C sur la ligne de commande, même si je l’ai tweaké un peu pour se comporter un peu mieux.

Ce hack est un hack en mode interactif uniquement, et je suis assez confiant pour ne le recommander à personne. Les commandes en arrière-plan sont susceptibles de provoquer un comportement encore moins défini que la normale. Les autres réponses sont un meilleur moyen d’obtenir des résultats par programme.


Ceci étant dit, voici la "solution":

Prompt_COMMAND='LAST="`cat /tmp/x`"; exec >/dev/tty; exec > >(tee /tmp/x)'

Définissez cette variable d’environnement bash et émettez les commandes souhaitées. $LAST aura généralement le résultat que vous recherchez:

startide seth> fortune
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve
startide seth> echo "$LAST"
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve
70
Seth Robertson

Je ne connais aucune variable qui fasse cela automatiquement. Pour faire autre chose que de simplement copier-coller le résultat, vous pouvez ré-exécuter ce que vous venez de faire, par exemple

vim $(!!)

!! Est une extension de l'historique signifiant "la commande précédente".

Si vous vous attendez à ce qu'il y ait un seul nom de fichier contenant des espaces ou d'autres caractères susceptibles d'empêcher l'analyse des arguments, citez le résultat (vim "$(!!)"). Si vous le laissez non mis entre guillemets, vous pourrez ouvrir plusieurs fichiers à la fois, à condition qu'ils n'incluent pas d'espaces ni d'autres jetons d'analyse Shell.

87
Daenyth

Bash est une sorte de langage moche. Oui, vous pouvez affecter la sortie à une variable

MY_VAR="$(find -name foo.txt)"
echo "$MY_VAR"

Mais il vaut mieux espérer que votre plus difficile reste que find n'ait retourné qu'un résultat et que ce résultat ne contienne aucun caractère "impair", comme les retours chariot ou les sauts de ligne, car ils seront modifiés en silence lorsqu'ils seront affectés à un Bash. variable.

Mais mieux vaut prendre soin de citer votre variable correctement lorsque vous l’utilisez!

Il est préférable d’agir directement sur le fichier, par exemple. avec find '-execdir (consultez le manuel).

find -name foo.txt -execdir vim '{}' ';'

ou

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'
20
rlibby

Il y a plus d'une façon de faire cela. Une solution consiste à utiliser v=$(command) qui affectera le résultat de la commande à v. Par exemple:

v=$(date)
echo $v

Et vous pouvez aussi utiliser des citations arrières.

v=`date`
echo $v

De Guide du débutant Bash ,

Lorsque la forme de substitution de style ancien citée en arrière est utilisée, la barre oblique inversée conserve son sens littéral sauf si elle est suivie de "$", "` "ou"\". Les premiers backticks non précédés d'une barre oblique inverse mettent fin à la substitution de commande. Lorsque vous utilisez le formulaire "$ (COMMAND)", tous les caractères entre les parenthèses constituent la commande; aucun n'est traité spécialement.

EDIT: Après le montage dans la question, il semble que ce n’est pas ce que l’OP cherche. Autant que je sache, il n'y a pas de variable spéciale comme $_ pour la sortie de la dernière commande.

12
taskinoor

C'est assez facile. Utilisez des guillemets:

var=`find . -name foo.txt`

Et puis, vous pouvez l'utiliser à tout moment dans le futur

echo $var
mv $var /somewhere
10
Wes Hardaker

Je pense que vous pourrez peut-être pirater une solution qui implique de définir votre shell sur un script contenant:

#!/bin/sh
bash | tee /var/log/bash.out.log

Ensuite, si vous définissez $Prompt_COMMAND pour sortir un délimiteur, vous pouvez écrire une fonction d’aide (appelée peut-être _) qui vous donne le dernier morceau de ce journal, vous pouvez donc l’utiliser comme:

% find lots*of*files
...
% echo "$(_)"
... # same output, but doesn't run the command again
9
Jay Adkisson

Disclamers:

  • Cette réponse est en retard six mois: D
  • Je suis un gros utilisateur de tmux
  • Vous devez exécuter votre Shell dans tmux pour que cela fonctionne

Lorsque vous exécutez un shell interactif dans tmux, vous pouvez facilement accéder aux données actuellement affichées sur un terminal. Jetons un coup d'oeil à quelques commandes intéressantes:

  • volet de capture de tmux: celui-ci copie les données affichées dans l'un des tampons internes du tmux. Il peut copier l'historique qui n'est actuellement pas visible, mais cela ne nous intéresse pas pour l'instant.
  • tmux list-buffers: ceci affiche les informations sur les tampons capturés. Le plus récent portera le numéro 0.
  • tmux show-buffer -b (buffer num): ceci affiche le contenu du tampon donné sur un terminal
  • tmux paste-buffer -b (buffer num): ceci colle le contenu du tampon donné en entrée

Oui, cela nous donne beaucoup de possibilités maintenant :) Quant à moi, j'ai mis en place un simple alias: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1" Et maintenant chaque fois que j'ai besoin d'accéder à la dernière ligne, j'utilise simplement $(L) pour l'obtenir.

Ceci est indépendant du flux de sortie utilisé par le programme (que ce soit stdin ou stderr), de la méthode d'impression (ncurses, etc.) et du code de sortie du programme - les données doivent simplement être affichées.

8
Wiesław Herr

Vous pouvez configurer l'alias suivant dans votre profil bash:

alias s='it=$($(history | tail -2 | head -1 | cut -d" " -f4-))'

Ensuite, en tapant 's' après une commande arbitraire, vous pouvez enregistrer le résultat dans une variable Shell 'it'.

Ainsi, un exemple d'utilisation serait:

$ which python
/usr/bin/python
$ s
$ file $it
/usr/bin/python: symbolic link to `python2.6'
7
Joe Tallett

Je viens de distiller cette fonction bash à partir des suggestions ici:

grab() {     
  grab=$("$@")
  echo $grab
}

Ensuite, vous faites juste:

> grab date
Do 16. Feb 13:05:04 CET 2012
> echo $grab
Do 16. Feb 13:05:04 CET 2012

Mise à jour: un utilisateur anonyme a suggéré de remplacer echo par printf '%s\n' qui a l’avantage de ne pas traiter d’options comme -e dans le texte saisi. Donc, si vous vous attendez à de telles particularités ou si vous les expérimentez, considérez cette suggestion. Une autre option consiste à utiliser cat <<<$grab au lieu.

6
Tilman Vogel

Capturez la sortie avec des backticks:

output=`program arguments`
echo $output
emacs $output
5
bandi

En disant "j'aimerais pouvoir utiliser le résultat de la dernière commande exécutée dans une commande ultérieure", je suppose - vous entendez le résultat de toute commande, pas seulement find.

Si c'est le cas - xargs est ce que vous recherchez.

find . -name foo.txt -print0 | xargs -0 -I{} mv {} /some/new/location/{}

OU si vous êtes intéressé pour voir la sortie en premier:

find . -name foo.txt -print0

!! | xargs -0 -I{} mv {} /some/new/location/{}

Cette commande traite plusieurs fichiers et fonctionne comme un charme même si le chemin et/ou le nom du fichier contient des espaces.

Notez la partie mv {}/some/new/location/{} de la commande. Cette commande est construite et exécutée pour chaque ligne imprimée par la commande précédente. Ici, la ligne imprimée par la commande précédente est remplacée à la place de {}.

Extrait de la page de manuel de xargs:

xargs - construit et exécute des lignes de commande à partir d'une entrée standard

Pour plus de détails, voir la page de manuel: man xargs

5
ssapkota

Je fais habituellement ce que les autres ici ont suggéré ... sans le devoir:

$find . -iname '*.cpp' -print
./foo.cpp
./bar.cpp
$vi `!!`
2 files to edit

Vous pouvez devenir meilleur si vous aimez:

$grep -R "some variable" * | grep -v tags
./foo/bar/xxx
./bar/foo/yyy
$vi `!!`
4
halm

Si tout ce que vous voulez, c'est réexécuter votre dernière commande et obtenir le résultat, une simple variable bash fonctionnerait:

LAST=`!!`

Vous pouvez alors exécuter votre commande sur la sortie avec:

yourCommand $LAST

Cela va générer un nouveau processus et relancer votre commande, puis vous donner la sortie. Cela ressemble à ce que vous voudriez vraiment être un fichier d’historique bash pour la sortie de commande. Cela signifie que vous devrez capturer la sortie que bash envoie à votre terminal. Vous pourriez écrire quelque chose à regarder le/dev ou/proc nécessaire, mais c'est compliqué. Vous pouvez également simplement créer un "tuyau spécial" entre votre terme et bash avec une commande té au milieu qui redirige vers votre fichier de sortie.

Mais les deux sont des solutions de type hacky. Je pense que la meilleure chose serait terminateur qui est un terminal plus moderne avec enregistrement de la sortie. Il suffit de consulter votre fichier journal pour connaître les résultats de la dernière commande. Une variable bash similaire à celle ci-dessus rendrait cela encore plus simple.

2
Spencer Rathbun

Voici un moyen de le faire après avoir exécuté votre commande et décidé de stocker le résultat dans une variable:

$ find . -name foo.txt
./home/user/some/directory/foo.txt
$ OUTPUT=`!!`
$ echo $OUTPUT
./home/user/some/directory/foo.txt
$ mv $OUTPUT somewhere/else/

Ou si vous savez à l'avance que vous voulez le résultat dans une variable, vous pouvez utiliser des backticks:

$ OUTPUT=`find . -name foo.txt`
$ echo $OUTPUT
./home/user/some/directory/foo.txt
1
Nate W.

vous pouvez utiliser !!: 1. Exemple:

~]$ ls *.~
class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 

~]$ rm !!:1
rm class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 


~]$ ls file_to_remove1 file_to_remove2
file_to_remove1 file_to_remove2

~]$ rm !!:1
rm file_to_remove1

~]$ rm !!:2
rm file_to_remove2
1
rkm

Comme alternative aux réponses existantes: Utilisez while si vos noms de fichier peuvent contenir des espaces comme ceci:

find . -name foo.txt | while IFS= read -r var; do
  echo "$var"
done

Comme je l'ai écrit, la différence n'est pertinente que si vous devez vous attendre à des blancs dans les noms de fichiers.

NB: le seul élément intégré ne concerne pas la sortie mais le statut de la dernière commande.

1
0xC0000022L

J'ai eu un besoin similaire, dans lequel je voulais utiliser la sortie de la dernière commande dans la suivante. Un peu comme un | (tuyau). par exemple

$ which gradle 
/usr/bin/gradle
$ ls -alrt /usr/bin/gradle

à quelque chose comme -

$ which gradle |: ls -altr {}

Solution: créé ce tuyau personnalisé. Vraiment simple, en utilisant xargs -

$ alias :='xargs -I{}'

Fondamentalement, rien en créant une main courte pour xargs, cela fonctionne comme un charme, et est vraiment pratique. Je viens d'ajouter l'alias dans le fichier .bash_profile.

1
eXc

Cela peut être fait en utilisant la magie des descripteurs de fichier et l'option lastpipe Shell.

Cela doit être fait avec un script - l'option "lastpipe" ne fonctionnera pas en mode interactif.

Voici le script que j'ai testé avec:

$ cat shorttest.sh 
#!/bin/bash
shopt -s lastpipe

exit_tests() {
    EXITMSG="$(cat /proc/self/fd/0)"
}

ls /bloop 2>&1 | exit_tests

echo "My output is \"$EXITMSG\""


$ bash shorttest.sh 
My output is "ls: cannot access '/bloop': No such file or directory"

Ce que je fais ici est:

  1. réglage de l'option shell shopt -s lastpipe. Cela ne fonctionnera pas sans cela car vous perdrez le descripteur de fichier.

  2. en s'assurant que mon stderr soit également capturé avec 2>&1

  3. canaliser la sortie dans une fonction afin que le descripteur de fichier stdin puisse être référencé.

  4. définir la variable en récupérant le contenu du /proc/self/fd/0 descripteur de fichier, qui est stdin.

J'utilise ceci pour capturer des erreurs dans un script, donc s'il y a un problème avec une commande, je peux arrêter le traitement du script et quitter immédiatement.

shopt -s lastpipe

exit_tests() {
    MYSTUFF="$(cat /proc/self/fd/0)"
    BADLINE=$BASH_LINENO
}

error_msg () {
    echo -e "$0: line $BADLINE\n\t $MYSTUFF"
    exit 1
}

ls /bloop 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg

De cette façon, je peux ajouter 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg derrière chaque commande que je tiens à vérifier.

Maintenant, vous pouvez profiter de votre sortie!

1
montjoy
find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/

est encore une autre façon, bien que sale.

0
Kevin

Ce n'est pas strictement une solution bash, mais vous pouvez utiliser la tuyauterie avec sed pour obtenir la dernière ligne des commandes précédentes.

D'abord, voyons ce que j'ai dans le dossier "a"

rasjani@helruo-dhcp022206::~$ find a
a
a/foo
a/bar
a/bat
a/baz
rasjani@helruo-dhcp022206::~$ 

Ensuite, votre exemple avec ls et cd se transformerait en sed & piping en quelque chose comme ceci:

rasjani@helruo-dhcp022206::~$ cd `find a |sed '$!d'`
rasjani@helruo-dhcp022206::~/a/baz$ pwd
/home/rasjani/a/baz
rasjani@helruo-dhcp022206::~/a/baz$

Ainsi, la magie réelle se produit avec sed, vous dirigez n'importe quelle sortie de quelle commande que ce soit dans sed et sed imprime la dernière ligne que vous pouvez utiliser comme paramètre avec des ticks arrière. Ou vous pouvez aussi combiner cela avec xargs. ("man xargs" dans Shell est ton ami)

0
rasjani

Le shell n'a pas de symboles spéciaux de type Perl qui stockent le résultat d'écho de la dernière commande.

Apprenez à utiliser le symbole pipe avec awk.

find . | awk '{ print "FILE:" $0 }'

Dans l'exemple ci-dessus, vous pouvez faire:

find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }'
0
ascotan