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

139

Je voudrais 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, ou le supprimer, ou faire autre chose avec lui, par exemple

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

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

Mettre à jour:

Pour clarifier, je ne veux pas attribuer les choses manuellement. Ce que je cherche, c'est quelque chose comme des 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.

Dernière mise à jour:

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

  • n'oubliez pas de le faire touch /tmp/xlorsque vous essayez la solution pour la toute première fois
  • le résultat ne sera stocké que si le code de sortie de la dernière commande a réussi
Armandino
la source
Après avoir vu votre modification, j'ai pensé supprimer ma réponse. Je me demande s'il y a quelque chose de intégré que vous recherchez.
taskinoor
Je n'ai rien trouvé de intégré. Je me demandais s'il serait possible de l'implémenter .. peut-être via .bahsrc? Je pense que ce serait une fonctionnalité très pratique.
armandino
Je crains que tout ce que vous puissiez faire est de rediriger la sortie vers un fichier ou un tube ou de la capturer, sinon elle ne sera pas enregistrée.
bandi
3
Vous ne pouvez pas faire cela sans la coopération du shell et du terminal, et ils ne coopèrent généralement pas. Voir aussi Comment réutiliser la dernière sortie de la ligne de commande? et Utilisation du texte de la sortie des commandes précédentes sur Unix Stack Exchange .
Gilles 'SO- arrête d'être diabolique'
6
L'une des principales raisons pour lesquelles la sortie des commandes n'est pas capturée est que la sortie peut être arbitrairement grande - plusieurs mégaoctets à la fois. Certes, pas toujours aussi gros, mais les gros rendements posent des problèmes.
Jonathan Leffler

Réponses:

71

C'est une solution vraiment pirate, mais elle semble fonctionner la plupart du temps une partie du temps. Pendant les tests, j'ai remarqué que cela ne fonctionnait parfois pas très bien lors de l'obtention d'un^C sur la ligne de commande, bien que je l'ai modifié un peu pour qu'il se comporte un peu mieux.

Ce hack est un hack en mode interactif uniquement, et je suis convaincu que je ne le recommanderais à 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.


Cela é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 émet les commandes souhaitées. $LASTaura généralement la sortie 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
Seth Robertson
la source
1
@armandino: Ceci fait bien sûr que les programmes qui s'attendent à interagir avec un terminal en sortie standard ne fonctionnent pas comme prévu (plus / moins) ou ne stockent pas des choses étranges dans $ LAST (emacs). Mais je pense que c'est à peu près aussi bon que vous allez l'obtenir. La seule autre option consiste à utiliser le script (type) pour enregistrer une copie de TOUT dans un fichier, puis à utiliser PROMPT_COMMAND pour vérifier les modifications depuis le dernier PROMPT_COMMAND. Cela inclura des choses que vous ne voulez pas, cependant. Je suis sûr que vous ne trouverez rien de plus proche de ce que vous voulez que cela.
Seth Robertson
2
Pour aller un peu plus loin: PROMPT_COMMAND='last="$(cat /tmp/last)";lasterr="$(cat /tmp/lasterr)"; exec >/dev/tty; exec > >(tee /tmp/last); exec 2>/dev/tty; exec 2> >(tee /tmp/lasterr)'qui fournit à la fois $lastet $lasterr.
ℝaphink
@cdosborn: man envoie par défaut la sortie via un pager. Comme mon commentaire précédent l'a dit: "les programmes qui s'attendent à interagir avec un terminal en sortie standard ne fonctionnent pas comme prévu (plus / moins)". de plus en moins sont des téléavertisseurs.
Seth Robertson
Je reçois:No such file or directory
Francisco Corrales Morales
@Raphink Je voulais juste signaler une correction: votre suggestion devrait se terminer 2> >(tee /tmp/lasterr 1>&2)car la sortie standard de teedoit être redirigée vers l'erreur standard.
Hugues
90

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

vim $(!!)

!! est l'expansion de l'historique signifiant «la commande précédente».

Si vous vous attendez à ce qu'il y ait un seul nom de fichier avec des espaces ou d'autres caractères pouvant empêcher l'analyse correcte des arguments, citez le résultat ( vim "$(!!)"). Le laisser sans guillemets permettra d'ouvrir plusieurs fichiers à la fois tant qu'ils n'incluent pas d'espaces ou d'autres jetons d'analyse du shell.

Daenyth
la source
1
Le copier-coller est ce que je fais habituellement dans un tel cas, également parce que vous n'avez généralement besoin que d'une partie de la sortie de la commande. Je suis surpris que vous soyez le premier à le mentionner.
Bruno De Fraine
4
Vous pouvez faire à peu près la même chose avec moins de frappes avec:vim `!!`
psmears
Pour voir la différence par rapport à la réponse acceptée, exécutez date "+%N"puisecho $(!!)
Marinos An
20

Bash est une sorte de langue laide. Oui, vous pouvez affecter la sortie à la variable

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

Mais mieux vaut espérer que votre plus dur findn'a retourné qu'un seul résultat et que ce résultat ne contenait 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 à une variable Bash.

Mais mieux vaut faire attention à bien citer votre variable lors de son utilisation!

Il vaut mieux agir directement sur le fichier, par exemple avec find's -execdir(consulter le manuel).

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

ou

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'
rlibby
la source
5
Ils ne sont pas modifiés en silence lorsque vous les assignez, ils sont modifiés lorsque vous effectuez un écho! Vous n'avez qu'à faire echo "${MY_VAR}"pour voir que c'est le cas.
paxdiablo
13

Il existe plusieurs façons de procéder. Une façon est d'utiliser v=$(command)qui affectera la sortie de la commande à v. Par exemple:

v=$(date)
echo $v

Et vous pouvez également utiliser des contre-citations.

v=`date`
echo $v

À partir du guide du débutant Bash ,

Lorsque la forme de substitution à l'ancienne est utilisée, la barre oblique inverse conserve sa signification littérale sauf lorsqu'elle est suivie de "$", "` "ou" \ ". Les premiers backticks non précédés d'une backslash mettent fin à la substitution de commande. Lorsque vous utilisez le formulaire "$ (COMMAND)", tous les caractères entre parenthèses constituent la commande; aucun n'est traité spécialement.

EDIT: Après la modification de la question, il semble que ce n'est pas ce que l'OP recherche. Autant que je sache, il n'y a pas de variable spéciale comme $_pour la sortie de la dernière commande.

taskinoor
la source
11

C'est assez simple. Utilisez des contre-citations:

var=`find . -name foo.txt`

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

echo $var
mv $var /somewhere
Wes Hardaker
la source
11

Disclamers:

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

Lors de l'exécution d'un shell interactif dans tmux, vous pouvez facilement accéder aux données actuellement affichées sur un terminal. Jetons un coup d'œil à quelques commandes intéressantes:

  • tmux capture-pane : 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 maintenant
  • tmux list-buffers : cela affiche les informations sur les tampons capturés. Le plus récent portera le numéro 0.
  • tmux show-buffer -b (buffer num) : ceci imprime le contenu du buffer donné sur un terminal
  • tmux paste-buffer -b (buffer num) : ceci colle le contenu du buffer donné en entrée

Ouais, 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 juste être affichées.

Wiesław Herr
la source
Hé, merci pour cette astuce tmux. Je cherchais fondamentalement la même chose que l'OP, j'ai trouvé votre commentaire et codé un script shell pour vous permettre de choisir et de coller une partie de la sortie d'une commande précédente en utilisant les commandes tmux que vous mentionnez et les mouvements Vim: github.com/ bgribble / lw
Bill Gribble
9

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

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

Ensuite, si vous définissez $PROMPT_COMMANDpour afficher un délimiteur, vous pouvez écrire une fonction d'assistance (peut-être appelée _) qui vous obtient 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
Jay Adkisson
la source
7

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'
Joe Tallett
la source
Je vous remercie! C'est ce dont j'avais besoin pour implémenter ma grabfonction qui copie la nième ligne de la dernière commande dans le presse-papiers gist.github.com/davidhq/f37ac87bc77f27c5027e
davidhq
6

Je viens de distiller cette bashfonction à partir des suggestions ici:

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

Ensuite, vous faites simplement:

> 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 echopar printf '%s\n'qui a l'avantage de ne pas traiter les options comme -edans le texte saisi. Donc, si vous attendez ou rencontrez de telles particularités, considérez cette suggestion. Une autre option consiste à utiliser à la cat <<<$grabplace.

Tilman Vogel
la source
1
Ok, à proprement parler ce n'est pas une réponse car, la partie "automatique" est complètement absente.
Tilman Vogel
Pouvez-vous expliquer l' cat <<<$graboption?
leoj
1
Citant de man bash: "Here Strings: Une variante des documents here, le format est: <<<wordLe mot est développé et fourni à la commande sur son entrée standard." Ainsi, la valeur de $grabest envoyée catsur stdin, et la catrenvoie simplement à stdout.
Tilman Vogel
5

En disant "j'aimerais pouvoir utiliser le résultat de la dernière commande exécutée dans une commande suivante", je suppose - vous voulez dire le résultat de n'importe quelle commande, pas seulement find.

Si tel est le cas, xargs est ce que vous recherchez.

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

OU si vous souhaitez 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 de 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 - construire et exécuter des lignes de commande à partir d'une entrée standard

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

ssapkota
la source
5

Capturez la sortie avec des backticks:

output=`program arguments`
echo $output
emacs $output
bandi
la source
4

Je fais généralement ce que les autres ici ont suggéré ... sans la tâche:

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

Vous pouvez devenir plus chic si vous aimez:

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

Si tout ce que vous voulez est de réexécuter votre dernière commande et d'obtenir la sortie, une simple variable bash fonctionnerait:

LAST=`!!`

Ainsi, vous pouvez exécuter votre commande sur la sortie avec:

yourCommand $LAST

Cela engendrera un nouveau processus et réexécutera votre commande, puis vous donnera la sortie. Cela ressemble à ce que vous aimeriez vraiment être un fichier d'historique bash pour la sortie des commandes. Cela signifie que vous devrez capturer la sortie que bash envoie à votre terminal. Vous pouvez écrire quelque chose pour regarder le / dev ou / proc nécessaire, mais c'est compliqué. Vous pouvez aussi simplement créer un "tube spécial" entre votre terme et bash avec une commande tee au milieu qui redirige vers votre fichier de sortie.

Mais ces deux solutions sont en quelque sorte des solutions piratées. Je pense que la meilleure chose serait Terminator qui est un terminal plus moderne avec journalisation de sortie. Vérifiez simplement votre fichier journal pour les résultats de la dernière commande. Une variable bash similaire à celle ci-dessus rendrait cela encore plus simple.

Spencer Rathbun
la source
1

Voici une façon de le faire après avoir exécuté votre commande et décidé que vous souhaitez 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 voudrez le résultat dans une variable, vous pouvez utiliser des accents inversés:

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

Comme alternative aux réponses existantes: à utiliser whilesi vos noms de fichiers peuvent contenir des espaces vides comme celui-ci:

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 l'état de la dernière commande.

0xC0000022L
la source
1

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
rkm
la source
Cette notation saisit l'argument numéroté d'une commande: Voir la documentation pour "$ {paramètre: offset}" ici: gnu.org/software/bash/manual/html_node/… . Voir aussi ici pour plus d'exemples: howtogeek.com/howto/44997/…
Alexander Bird
1

J'avais 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: a 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 du charme, et est vraiment pratique. J'ajoute simplement l'alias dans le fichier .bash_profile.

eXc
la source
1

Cela peut être fait en utilisant la magie des descripteurs de fichiers et lastpipe option shell.

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

Voici le script avec lequel j'ai testé:

$ 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, c'est:

  1. définition 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 est également capturé avec 2>&1

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

  4. définir la variable en obtenant le contenu du /proc/self/fd/0descripteur 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_msgderrière chaque commande que je souhaite vérifier.

Vous pouvez maintenant profiter de votre sortie!

Montjoy
la source
0

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

Voyons d'abord 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 la sortie de la commande dans sed et sed imprime la dernière ligne que vous pouvez utiliser comme paramètre avec des graduations inverses. Ou vous pouvez également combiner cela à xargs. ("man xargs" dans shell est votre ami)

Rasjani
la source
0

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 de tuyau 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" }'
ascotan
la source
0
find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/

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

Kevin
la source
trouver . -name foo.txt 1> tmpfile && mv `cat tmpfile` chemin / vers / certains / dir && rm tmpfile
Kevin
0

Je trouve que se souvenir de diriger la sortie de mes commandes dans un fichier spécifique est un peu ennuyeux, ma solution est une fonction dans mon .bash_profile qui capture la sortie dans un fichier et renvoie le résultat lorsque vous en avez besoin.

L'avantage de celui-ci est que vous n'avez pas à réexécuter toute la commande (lors de l'utilisation findou d'autres commandes de longue durée qui peuvent être critiques)

Assez simple, collez ceci dans votre .bash_profile:

Scénario

# catch stdin, pipe it to stdout and save to a file
catch () { cat - | tee /tmp/catch.out}
# print whatever output was saved to a file
res () { cat /tmp/catch.out }

Usage

$ find . -name 'filename' | catch
/path/to/filename

$ res
/path/to/filename

À ce stade, j'ai tendance à simplement ajouter | catchà la fin de toutes mes commandes, car cela ne coûte rien et cela m'évite d'avoir à réexécuter des commandes qui prennent beaucoup de temps à terminer.

De plus, si vous souhaitez ouvrir la sortie du fichier dans un éditeur de texte, vous pouvez le faire:

# vim or whatever your favorite text editor is
$ vim <(res)
Connor
la source