Comment exécuter la sortie d'une commande dans le shell actuel?

89

Je connais bien l' utilitaire source(aka .), qui prendra le contenu d'un fichier et l'exécutera dans le shell actuel.

Maintenant, je transforme du texte en commandes shell, puis je les exécute, comme suit:

$ ls | sed ... | sh

lsest juste un exemple aléatoire, le texte original peut être n'importe quoi. sedaussi, juste un exemple pour transformer du texte. Le peu intéressant est sh. Je pipe tout ce que je suis arrivé shet il le fait fonctionner.

Mon problème est que cela signifie démarrer un nouveau sous-shell. Je préfère que les commandes soient exécutées dans mon shell actuel. Comme je pourrais le faire avec source some-file, si j'avais les commandes dans un fichier texte.

Je ne veux pas créer de fichier temporaire car je me sens sale.

Sinon, j'aimerais démarrer mon sous-shell avec exactement les mêmes caractéristiques que mon shell actuel.

mise à jour

Ok, les solutions utilisant le backtick fonctionnent certainement, mais je dois souvent le faire pendant que je vérifie et modifie la sortie, donc je préférerais de loin s'il y avait un moyen de canaliser le résultat dans quelque chose à la fin.

triste mise à jour

Ah, la /dev/stdinchose avait l'air si jolie, mais, dans un cas plus complexe, cela n'a pas fonctionné.

Donc, j'ai ceci:

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/git mv -f $1 $2.doc/i' | source /dev/stdin

Ce qui garantit que tous les .docfichiers ont leur extension en minuscule.

Et qui, d'ailleurs, peut être manipulé xargs, mais ce n'est pas le problème.

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/$1 $2.doc/i' | xargs -L1 git mv

Donc, quand je lance le premier, il se termine tout de suite, rien ne se passe.

kch
la source
Votre commande complexe fonctionne-t-elle lorsque vous dirigez d'abord vers un fichier temporaire, puis que vous le sourcez? Sinon, quel est le problème avec la sortie générée? La sortie de votre commande ne fonctionnera pas si vos noms de fichiers contiennent des espaces ou si certaines séquences ne sont pas correctement échappées. Je voudrais ajouter des citations autour de 1 $ et 2 $ doc au minimum.
Kaleb Pederson
Y a-t-il une bonne raison de devoir l'exécuter dans le shell d'origine? - ces exemples ne manipulent pas le shell actuel donc vous ne gagnez rien en le faisant. La solution rapide est de rediriger la sortie vers un fichier et de fournir ce fichier
nos
@kaleb la sortie fonctionne correctement. dans ce cas particulier, même si je pipe vers sh. les noms de fichiers sont sans espace, mais merci de le noter. @nos variables d'environnement git sur le shell d'origine. et encore une fois, ce ne sont que des exemples. la question est pour la vie.
kch
source / dev / stdin ne fonctionnait pas pour moi quand j'avais besoin de variables assignées pour rester. geirha sur freenode bash m'a pointé vers mywiki.wooledge.org/BashFAQ/024 et m'a suggéré d'essayer une source de substitution de processus <(commande) qui a fonctionné pour moi
srcerer

Réponses:

85
$ ls | sed ... | source /dev/stdin

MISE À JOUR: Cela fonctionne dans bash 4.0, ainsi que tcsh et dash (si vous passez sourceà .). Apparemment, c'était buggy dans bash 3.2. À partir des notes de publication de bash 4.0 :

Correction d'un bug qui causait "." pour ne pas lire et exécuter des commandes à partir de fichiers non réguliers tels que des périphériques ou des canaux nommés

mark4o
la source
D'accord. Passons à bash 4.0 alors.
kch
Oh, et puisque vous listez les shells, cela fonctionne aussi dans zsh.
kch
1
Je pensais que c'était ingénieux jusqu'à ce que je l'ai essayé dans msys / mingw (où il n'y a pas de dossier / dev (ni même de périphériques)! J'ai essayé de nombreuses combinaisons d'eval, $ () et de backticks ''. Je ne pouvais rien faire fonctionner , donc j'ai finalement juste redirigé la sortie dans un fichier temporaire, j'ai récupéré le fichier temporaire et je l'ai supprimé. Fondamentalement, "sed ...> /tmp/$$.tmp &&. /tmp/$$.tmp && rm / tmp / $$. tmp ". Tout le monde a une solution msys / mingw sans fichiers temporaires ???
chriv
10
Juste une remarque: celui-ci ne semble pas gérer les instructions d'exportation comme prévu. Si la chaîne canalisée a une instruction d'exportation, la variable exportet n'est pas disponible dans votre environnement de terminal par la suite, alors qu'avec eval export fonctionne également parfaitement.
Phil
1
Étant donné que MacOS X a généralement bash 3.2 (peut-être que Yosemite a 4.0?), Et le besoin de supercherie du dernier tuyau dans mon cas d'utilisation (exporter toutes les variables d'un fichier en pré-traitant chaque ligne avec sed pour ajouter `` export ''), j'ai choisi pour aller avec l' eval "$(sed ...)"approche - mais merci pour le heads-up 3.2 bug!
Alex Dupuy
133

La evalcommande existe précisément dans ce but.

eval "$( ls | sed... )"

En savoir plus sur le manuel bash :

évaluer

          eval [arguments]

Les arguments sont concaténés en une seule commande, qui est ensuite lue et exécutée, et son état de sortie renvoyé comme état de sortie de eval. S'il n'y a pas d'arguments ou seulement des arguments vides, l'état de retour est zéro.

Juliano
la source
8
Le seul problème ici est que vous devrez peut-être insérer des; pour séparer vos commandes. J'utilise moi-même cette méthode pour travailler sous AIX, Sun, HP et Linux.
Tanktalus
Tanktalus, merci pour ce commentaire, cela a fait fonctionner mon script. Sur ma machine, eval ne sépare pas les commandes sur les nouvelles lignes et l'utilisation de la source ne fonctionne pas même avec des points-virgules. L'évaluation avec des points-virgules est la solution. J'aimerais pouvoir vous donner quelques points.
Milan Babuškov
2
@ MilanBabuškov: Je l'ai classé pour vous ;-)
Phil
3
C'est excellent. Cependant, pour mon cas d'utilisation, j'ai fini par avoir à mettre des guillemets autour de mon $ (), comme ceci: eval "$( ssh remote-host 'cat ~/.bash_profile' )"Notez que j'utilise bien bash 3.2.
Zachary Murray
3
Cela fonctionne pour moi là où la réponse acceptée n'a pas fonctionné.
Dan Tenenbaum
37

Wow, je sais que c'est une vieille question, mais je me suis retrouvé avec le même problème exact récemment (c'est comme ça que je suis arrivé ici).

Quoi qu'il en soit - je n'aime pas la source /dev/stdinréponse, mais je pense que j'en ai trouvé une meilleure. C'est trompeusement simple en fait:

echo ls -la | xargs xargs

Bonne droite? En fait, cela ne fait toujours pas ce que vous voulez, car si vous avez plusieurs lignes, cela les concatera en une seule commande au lieu d'exécuter chaque commande séparément. Donc, la solution que j'ai trouvée est:

ls | ... | xargs -L 1 xargs

l' -L 1option signifie que vous utilisez (au plus) 1 ligne par exécution de commande. Remarque: si votre ligne se termine par un espace de fin, elle sera concaténée avec la ligne suivante! Assurez-vous donc que chaque ligne se termine par un non-espace.

Enfin, vous pouvez faire

ls | ... | xargs -L 1 xargs -t

pour voir quelles commandes sont exécutées (-t est détaillé).

J'espère que quelqu'un lit ça!

Rabensky
la source
30

Essayez d'utiliser la substitution de processus , qui remplace la sortie d'une commande par un fichier temporaire qui peut ensuite être récupéré:

source <(echo id)
rishta
la source
7
De quel genre de sorcellerie s'agit-il?
paulotorrens
C'était aussi ma première pensée. Bien que j'aime mieux la solution eval. @PauloTorrens <(xyz) exécute simplement xyz et remplace <(xyz) par le nom d'un fichier qui écrira la sortie de xyz. Il est vraiment facile de comprendre comment cela fonctionne en faisant par exemple echo <(echo id)/dev/fd/12cat <(echo id)idsource <(echo id)id
:, en
2
@PauloTorrens, c'est ce qu'on appelle la substitution de processus . Voir les documents liés pour une explication officielle, mais la réponse courte est que "<()" est une syntaxe spéciale qui a été conçue pour des cas comme celui-ci à l'esprit.
Mark Stosberg
1
@MarkStosberg, savez-vous s'il s'agit d'une syntaxe spéciale ou simplement d'un sous-shell (...)redirigé?
paulotorrens
2
@PauloTorrens, C'est une syntaxe spéciale juste pour la substitution de processus.
Mark Stosberg
6

Je pense que c'est "la bonne réponse" à la question:

ls | sed ... | while read line; do $line; done

Autrement dit, on peut faire une whileboucle; la readcommande commande prend une ligne de son stdinet l'affecte à la variable $line. $linedevient alors la commande exécutée dans la boucle; et il continue jusqu'à ce qu'il n'y ait plus de lignes dans son entrée.

Cela ne fonctionnera toujours pas avec certaines structures de contrôle (comme une autre boucle), mais cela correspond à la facture dans ce cas.

Geoff Nixon
la source
5
`ls | sed ...`

Je sorte de sensation comme ls | sed ... | source -serait plus joli, mais malheureusement , sourcene comprend pas -signifier stdin.

le chaos
la source
après avoir vu la réponse de mark4o, n'est-ce pas comme si c'était juste dans nos visages tout ce temps?
kch
Heh, ouais. Je ne me souviens jamais que ce truc existe.
chaos
1

Je pense que votre solution est la substitution de commandes avec des backticks: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

Voir section 3.4.5

Eric Wendelin
la source
3
Si vous utilisez bash, la $( )syntaxe peut être préférée. 'Course backticks work in more shells ...
dmckee --- ex-moderator kitten
6
$ (commande) et $ ((1 + 1)) fonctionnent dans tous les shells posix. Je pense que beaucoup sont rebutés par le fait que vim les marque comme des erreurs de syntaxe, mais c'est simplement parce que vim met en évidence pour le shell Bourne original que très peu utilisent. Pour que vim soit correctement mis en surbrillance, mettez ceci dans votre .vimrc: let g: is_posix = 1
pixelbeat
1

Pour utiliser la solution de mark4o sur bash 3.2 (macos), une chaîne here peut être utilisée à la place des pipelines comme dans cet exemple:

. /dev/stdin <<< "$(grep '^alias' ~/.profile)"
ish-ouest
la source
ou utilisez des backticks:. / dev / stdin <<< `grep '^ alias' ~ / .profile`
William H. Hooper
0

Pourquoi ne pas l'utiliser sourcealors?

$ ls | sed ... > out.sh ; source out.sh
Kaleb Pederson
la source
Il a mentionné que les fichiers temporaires sont dégoûtants.
chaos
Oh ouais, j'ai raté ça :(.
Kaleb Pederson