Exécutez deux commandes sur un argument (sans script)

28

Comment puis-je exécuter deux commandes sur une entrée, sans taper deux fois cette entrée?

Par exemple, la commande stat en dit long sur un fichier, mais n'indique pas son type de fichier:

stat fileName

La commande file indique le type d'un fichier:

file fileName

Vous pouvez effectuer cela en une seule ligne de cette façon:

stat fileName ; file fileName

Cependant, vous devez taper deux fois le nom de fichier.

Comment pouvez-vous exécuter les deux commandes sur la même entrée (sans taper l'entrée ou une variable de l'entrée deux fois)?

Sous Linux, je sais comment diriger les sorties, mais comment canaliser les entrées?

Lonniebiz
la source
14
Juste pour être clair, ce ne sont pas des "entrées", ce sont des arguments (aussi appelés paramètres). L'entrée peut être canalisée via <(entrée du fichier vers le côté gauche) ou |(entrée du flux vers le côté droit). Il y a une différence.
goldilocks
6
Pas une réponse à votre question, mais peut-être une réponse à votre problème: En bash, la yank-last-argcommande (raccourci par défaut Meta-.ou Meta-_; Metaétant souvent la Alttouche gauche ) copiera le dernier paramètre de la ligne de commande précédente dans la ligne courante. Ainsi, après l'exécution, stat fileNamevous tapez file [Meta-.]et vous n'avez pas besoin de le taper à fileNamenouveau. J'utilise toujours ça.
Dubu
2
Et la différence est que <et >sont redirection , pas la tuyauterie . Un pipeline connecte deux processus, tandis que la redirection réaffecte simplement stdin / stdout.
bsd
@bdowning: Oui, mais vous redirigez le tuyau, n'est-ce pas? Vous pouvez rediriger le début d'un pipeline pour lire à partir d'un fichier sur disque, ou vous pouvez rediriger la fin d'un pipeline pour écrire dans un fichier sur disque. Passepoil toujours. ;-)
James Haigh
Connexes (mais plus spécialisées): Existe
G-Man dit `` Réintègre Monica ''

Réponses:

22

Voici une autre façon:

$ stat filename && file "$_"

Exemple:

$ stat /usr/bin/yum && file "$_"
  File: ‘/usr/bin/yum’
  Size: 801         Blocks: 8          IO Block: 4096   regular file
Device: 804h/2052d  Inode: 1189124     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:rpm_exec_t:s0
Access: 2014-06-11 22:55:53.783586098 +0700
Modify: 2014-05-22 16:49:35.000000000 +0700
Change: 2014-06-11 19:15:30.047017844 +0700
 Birth: -
/usr/bin/yum: Python script, ASCII text executable

Cela fonctionne dans bashet zsh. Cela fonctionne également dans mkshet dashmais uniquement lorsqu'il est interactif. Dans AT&T ksh, cela ne fonctionne que lorsque le file "$_"est sur une ligne différente de statcelle.

cuonglm
la source
1
Celui- !$ci ne fonctionnera pas car se !$développe jusqu'au dernier mot du dernier événement d'historique (donc de la ligne de commande entrée précédemment, pas de la commande stat).
Stéphane Chazelas
@ StéphaneChazelas: Oh, oui, mon erreur, je l'ai supprimé.
cuonglm
32

Je vais probablement faire raper mes phalanges pour cela, voici une combinaison hacky d'expansion bash brace et d'évaluation qui semble faire l'affaire

eval {stat,file}" fileName;"
iruvar
la source
8
Désolé, mais je ne peux pas résister .
Andreas Wiese
2
l'expansion de l'accolade est née csh, non bash. bashcopié de ksh. Ce code fonctionnera dans tous les csh, tcsh, ksh, zsh, fish et bash.
Stéphane Chazelas
1
Dommage que vous perdiez la possibilité de "tabuler" (auto-complétion) le nom de fichier, en raison de la citation.
Lonniebiz
Désolé, mais je n'ai pas pu résister non plus
tomd
désolé quel est le problème ici eval? (se référant aux meilleurs commentaires)
user13107
28

Avec zsh, vous pouvez utiliser des fonctions anonymes:

(){stat $1; file $1} filename

Avec eslambdas:

@{stat $1; file $1} filename

Vous pourriez également faire:

{ stat -; file -;} < filename

(faire le statpremier car le filemettra à jour le temps d'accès).

Je ferais:

f=filename; stat "$f"; file "$f"

cependant, c'est à cela que servent les variables.

Stéphane Chazelas
la source
3
{ stat -; file -;} < filenameest magique. statdoit vérifier pour voir que son stdin est connecté à un fichier et signaler les attributs du fichier? Vraisemblablement, ne pas faire avancer le pointeur sur stdin, ce qui permet filede renifler le contenu de stdin
iruvar
1
@ 1_CR, oui. stat -dit statde faire un fstat()sur son fd 0.
Stéphane Chazelas
4
La { $COMMAND_A -; $COMMAND_B; } < filenamevariante ne fonctionnera pas dans le cas général si $ COMMAND_A lit réellement une entrée; par exemple { cat -; cat -; } < filename, imprime le contenu du nom de fichier une seule fois.
Max Nanasy
2
stat -est géré spécialement depuis coreutils-8.0 (octobre 2009), dans les versions antérieures, vous obtiendrez une erreur (sauf si vous avez un fichier appelé à -partir d'une expérience précédente, auquel cas vous obtiendrez les mauvais résultats ...)
mr .spuratic
1
@ mr.spuratic. Oui, et les statistiques BSD, IRIX et zsh ne le reconnaîtront pas non plus. Avec zsh et IRIX stat, vous pouvez utiliser à la stat -f0place.
Stéphane Chazelas
9

Toutes ces réponses me semblent plus ou moins des scripts. Pour une solution qui n'implique vraiment aucun script et suppose que vous êtes assis au clavier en tapant dans un shell, vous pouvez essayer:

Enter
fichier de statistiques stat Esc_   Enter

En bash, zsh et ksh au moins, en modes vi et emacs, en appuyant sur Echap, puis en soulignant insère le dernier mot de la ligne précédente dans le tampon d'édition de la ligne actuelle.

Ou, vous pouvez utiliser la substitution d'historique:

Enter
fichier de statistiques stat ! $Enter

Dans bash, zsh, csh, tcsh et la plupart des autres shells, !$est remplacé par le dernier mot de la ligne de commande précédente lorsque la ligne de commande actuelle est analysée.

godlygeek
la source
Quelles sont les autres options disponibles dans zsh / bash Esc _? Pouvez-vous faire un lien avec la documentation officielle?
Abhijeet Rastogi
1
zsh: linux.die.net/man/1/zshzle et recherchez "insert-last-word"
godlygeek
1
@shadyabhi ^ Liens pour les raccourcis clavier bash et zsh. Notez que bash utilise simplement readline, donc (la plupart) celles de bash fonctionneront dans n'importe quelle application qui utilise la bibliothèque readline.
godlygeek
3

La façon dont j'ai trouvé cela raisonnablement simple consiste à utiliser xargs, il prend un fichier / pipe et convertit son contenu en arguments de programme. Cela peut être combiné avec un tee qui divise un flux et l'envoie à deux programmes ou plus. Dans votre cas, vous avez besoin de:

echo filename | tee >(xargs stat) >(xargs file) | cat

Contrairement à beaucoup d'autres réponses, cela fonctionnera dans bash et la plupart des autres shells sous Linux. Je suggérerai que c'est un bon cas d'utilisation d'une variable mais si vous ne pouvez absolument pas en utiliser une, c'est la meilleure façon de le faire uniquement avec des tuyaux et des utilitaires simples (en supposant que vous n'avez pas de shell particulièrement sophistiqué).

De plus, vous pouvez le faire pour n'importe quel nombre de programmes en les ajoutant simplement de la même manière que ces deux après le tee.

MODIFIER

Comme suggéré dans les commentaires, il y a quelques défauts dans cette réponse, le premier est le potentiel d'entrelacement de sortie. Cela peut être résolu comme suit:

echo filename | tee >(xargs stat) >(( wait $!; xargs file )) | cat

Cela forcera les commandes à s'exécuter à tour de rôle et la sortie ne sera jamais entrelacée.

Le deuxième problème est d'éviter la substitution de processus qui n'est pas disponible dans certains shells, ceci peut être réalisé en utilisant tpipe au lieu de tee comme suit:

echo filename | tpipe 'xargs stat' | ( wait $!; xargs file )

Cela devrait être portable sur presque n'importe quel shell (j'espère) et résout les problèmes dans l'autre recommandation, cependant j'écris ce dernier de mémoire car mon système actuel n'a pas de tpipe disponible.

Vality
la source
1
La substitution de processus est une fonctionnalité ksh qui ne fonctionne que dans ksh(pas les variantes du domaine public), bashet zsh. Notez que statet files'exécutera simultanément, donc leur sortie sera peut-être entrelacée (je suppose que vous utilisez | catpour forcer la mise en mémoire tampon de sortie pour limiter cet effet?).
Stéphane Chazelas
@ StéphaneChazelas Vous avez raison sur le chat, lorsque je l'utilise, je n'ai jamais réussi à produire un cas d'entrée entrelacée lors de son utilisation. Cependant, je vais essayer quelque chose pour produire momentanément une solution plus portable.
Vality
3

Cette réponse est similaire à quelques autres:

stat nom de fichier   && fichier! #: 1

Contrairement aux autres réponses, qui répètent automatiquement le dernier argument de la commande précédente, celle-ci vous oblige à compter:

ls -ld nom   && fichier #: 2

Contrairement à certaines des autres réponses, cela ne nécessite pas de guillemets:

stat "useless cat"  &&  file !#:1

ou

echo Sometimes a '$cigar' is just a !#:3.
Scott
la source
2

Ceci est similaire à quelques autres réponses, mais est plus portable que celui-ci et beaucoup plus simple que celui-ci :

sh -c 'stat "$ 0"; fichier "$ 0" ' nom_fichier

ou

sh -c 'stat "$ 1"; fichier "$ 1" 'sh nom de fichier

dans lequel shest affecté à $0. Bien qu'il y ait trois caractères de plus à taper, ce (deuxième) formulaire est préférable car c'est $0le nom que vous donnez à ce script en ligne. Il est utilisé, par exemple, dans les messages d'erreur par le shell pour ce script, comme quand fileou statne peut pas être trouvé ou ne peut pas être exécuté pour une raison quelconque.


Si vous voulez faire

stat nom de fichier 1  nom de fichier 2  nom de fichier 3 …; fichier nom de fichier 1  nom de fichier 2  nom de fichier 3

pour un nombre arbitraire de fichiers, faites

sh -c 'stat "$ @"; fichier "$ @" 'sh nom_fichier 1  nom_fichier 2  nom_fichier 3

qui fonctionne car "$@"est équivalent à "$1" "$2" "$3" ….

Scott
la source
C'est $0le nom que vous souhaitez donner à ce script en ligne. Il est utilisé par exemple dans les messages d'erreur par le shell pour ce script. Comme quand fileou statne peut pas être trouvé ou ne peut pas être exécuté pour une raison quelconque. Donc, vous voulez quelque chose de significatif ici. S'il n'est pas inspiré, utilisez-le sh.
Stéphane Chazelas
1

Je pense que vous posez une mauvaise question, du moins pour ce que vous essayez de faire. statet les filecommandes prennent des paramètres qui sont le nom d'un fichier et non son contenu. C'est plus tard la commande qui fait la lecture du fichier identifié par le nom que vous spécifiez.

Un tuyau est censé avoir deux extrémités, l'une utilisée en entrée et l'autre en sortie, ce qui est logique car vous devrez peut-être effectuer un traitement différent en cours de route avec différents outils jusqu'à obtenir le résultat souhaité. Dire que vous savez comment diriger les sorties mais ne savez pas comment diriger les entrées n'est pas correct en principe.

Dans votre cas, vous n'avez pas du tout besoin d'un tube car vous devez fournir l'entrée sous la forme d'un nom de fichier. Et même si ces outils ( statet file) liraient stdin, le canal n'est plus pertinent ici car l'entrée ne devrait être modifiée par aucun des outils quand il arrivera au second.

vadimbog
la source
1

Cela devrait fonctionner pour tous les noms de fichiers dans le répertoire en cours.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *
mikeserv
la source
1
En supposant que les noms de fichier ne contiennent pas de guillemets doubles, barre oblique inverse, dollar, backtick, }... et ne commencent pas par -. Certaines printfimplémentations (zsh, bash, ksh93) ont un %q. Avec zsh, ça pourrait être:printf 'stat %q; file %1$q\n' ./* | zsh
Stéphane Chazelas
@StephaneChezales - tout cela est corrigé maintenant, sauf pour la possibilité de la citation difficile. Je pourrais utiliser cela avec un seul, sed s///je suppose, mais c'est à peu près de la portée - et si les gens mettent cela dans leurs noms de fichiers, ils devraient utiliser Windows.
mikeserv
@ StéphaneChazelas - peu importe - j'ai oublié à quel point ceux-ci étaient faciles à gérer.
mikeserv
À proprement parler, ce n'est pas une réponse à la question posée: «Comment pouvez-vous exécuter les deux commandes sur la même entrée ( sans taper l'entrée… deux fois )?» (Non souligné dans l'original). Votre réponse s'applique à tous les fichiers du répertoire actuel - et elle comprend *deux fois . Autant dire stat -- *; file -- *.
Scott
@Scott - l'entrée n'est pas tapée deux fois - *est développée. L' extension de *plus les deux commandes pour chaque argument dans * est l'entrée. Voir? printf commands\ %s\;shift *
mikeserv
1

Il y a quelques références différentes à la «contribution» ici, donc je vais donner quelques scénarios en le comprenant d'abord. Pour votre réponse rapide à la question sous la forme la plus courte :

stat testfile < <($1)> outputfile

Ce qui précède effectuera une statistique sur le fichier de test, prendra (redirigera) son STDOUT et l'inclura dans la fonction spéciale suivante (la partie <()), puis affichera les résultats finaux de tout ce qui était, dans un nouveau fichier (fichier de sortie). Le fichier est appelé, puis référencé avec les fonctions intégrées bash (1 $ à chaque fois, jusqu'à ce que vous commenciez un nouvel ensemble d'instructions).

Votre question est excellente, et il existe plusieurs réponses et façons de le faire, mais elle change en effet avec ce que vous faites spécifiquement.

Par exemple, vous pouvez également boucler cela, ce qui est assez pratique. Un usage courant de ceci est, dans l'esprit de pseudo-code, est:

run program < <($output_from_program)> my_own.log

Prendre cela et développer ces connaissances vous permet de créer des choses telles que:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

Cela exécutera un simple ls -A sur votre répertoire actuel, puis indiquera pendant que vous parcourrez chaque résultat du ls -A vers (et voici où c'est délicat!) Grep "thatword" dans chacun de ces résultats, et exécutez uniquement le précédent printf (en rouge) s'il a effectivement trouvé un fichier contenant "ce mot". Il enregistrera également les résultats de la grep dans un nouveau fichier texte, files_that_matched_thatword.

Exemple de sortie:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

index.html

Tout cela a simplement imprimé le résultat ls -A, rien de spécial. Ajoutez-y quelque chose à grep cette fois:

echo "thatword" >> newfile

Maintenant, relancez-le:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

files_that_matched_thatword  index.html  newfile

Found a file: newfile:thatword

Bien que la réponse soit peut-être plus épuisante que ce que vous recherchez actuellement, je pense que conserver des notes pratiques comme celle-ci vous sera beaucoup plus utile dans les efforts futurs.

impulsion
la source