Führe zwei Befehle mit einem Argument aus (ohne Scripting)


28

Wie kann ich zwei Befehle für eine Eingabe ausführen, ohne diese Eingabe zweimal eingeben zu müssen?

Zum Beispiel sagt der Befehl stat viel über eine Datei aus, gibt jedoch nicht ihren Dateityp an:

stat fileName

Der Befehl file gibt an, um welchen Dateityp es sich handelt:

file fileName

Sie können dies auf folgende Weise in einer Zeile ausführen:

stat fileName ; file fileName

Sie müssen den Dateinamen jedoch zweimal eingeben.

Wie können Sie beide Befehle für dieselbe Eingabe ausführen (ohne die Eingabe oder eine Variable der Eingabe zweimal einzugeben)?

Unter Linux weiß ich, wie Ausgaben weitergeleitet werden, aber wie werden Eingaben weitergeleitet?


14
Nur um klar zu sein, das sind keine "Eingaben", sondern Argumente (auch als Parameter bezeichnet). Die Eingabe kann über <(Eingabe von der Datei zur linken Seite) oder |(Eingabe vom Stream zur rechten Seite) weitergeleitet werden. Es besteht ein Unterschied.
Goldlöckchen

6
Keine Antwort auf Ihre Frage, aber vielleicht eine Antwort auf Ihr Problem: In bash, die yank-last-argBefehl (Standard - Tastenkürzel Meta-.oder Meta-_, Metaoft der linke sein AltSchlüssel) wird den letzten Parameter aus der vorhergehenden Befehlszeile in die aktuellen kopieren. Nach der Ausführung geben stat fileNameSie also ein file [Meta-.]und müssen das nicht noch fileNameeinmal eingeben. Ich benutze das immer.
Dubu

2
Und der Unterschied ist , dass <und >sind Umleitung , nicht kochend . Eine Pipeline verbindet zwei Prozesse, während die Umleitung einfach stdin / stdout neu zuordnet.
Bsd

@bdowning: Ja, aber Sie leiten die Pipe um, nicht wahr? Sie können den Start einer Pipeline umleiten, um von einer Datei auf dem Datenträger zu lesen, oder Sie können das Ende einer Pipeline umleiten, um in eine Datei auf dem Datenträger zu schreiben. Immer noch Rohrleitungen. ;-)
James Haigh

Antworten:


21

Hier ist ein anderer Weg:

$ stat filename && file "$_"

Beispiel:

$ 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

Das funktioniert in bashund zsh. Das funktioniert auch in mkshund dashaber nur wenn interaktiv. In AT & T ksh funktioniert dies nur, wenn sich das file "$_"in einer anderen Leitung als die stateine befindet.


1
Der !$funktioniert nicht, da er !$bis zum letzten Wort des letzten Verlaufsereignisses erweitert wird (also von der zuvor eingegebenen Befehlszeile, nicht vom Befehl stat).
Stéphane Chazelas

@ StéphaneChazelas: Oh ja, mein Fehler, ich habe es gelöscht.
Cuonglm

32

Hier ist eine hackige Kombination aus Bash Brace Expansion und Eval, die den Trick zu machen scheint

eval {stat,file}" fileName;"

8
Entschuldigung, aber ich kann nicht widerstehen .
Andreas Wiese

2
Klammererweiterung entstand in csh, nicht bash. bashkopierte es von ksh. Dieser Code funktioniert in allen csh, tcsh, ksh, zsh, fish und bash.
Stéphane Chazelas

1
Schade, dass Sie aufgrund des Zitats die Möglichkeit verlieren, den Dateinamen "auszublenden" (automatisch zu vervollständigen).
Lonniebiz

Sorry, aber ich konnte nicht widerstehen entweder
Tomd

Entschuldigung, mit was ist das Problem hier eval? (unter Bezugnahme auf die Top-Kommentare)
user13107

28

Mit zshkönnen Sie anonyme Funktionen nutzen:

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

Mit esLambdas:

@{stat $1; file $1} filename

Sie könnten auch tun:

{ stat -; file -;} < filename

(Wenn Sie das statErste tun, filewird die Zugriffszeit aktualisiert).

Ja, würde ich:

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

Dafür sind aber Variablen da.


3
{ stat -; file -;} < filenameist magisch. statmuss überprüft werden, ob die stdin mit einer Datei verbunden ist und die Dateiattribute gemeldet werden? Vermutlich wird der Zeiger auf stdin nicht file
weiterbewegt,

1
@ 1_CR, ja. stat -sagt stat, ein fstat()auf seiner fd 0 zu tun .
Stéphane Chazelas

4
Die { $COMMAND_A -; $COMMAND_B; } < filenameVariante funktioniert im Allgemeinen nicht, wenn $ COMMAND_A tatsächlich eine Eingabe liest. zB { cat -; cat -; } < filenamewird der Inhalt des Dateinamens nur einmal gedruckt.
Max Nanasy

2
stat -wird speziell seit coreutils-8.0 (Okt. 2009) behandelt. In früheren Versionen erhalten Sie eine Fehlermeldung (es sei denn, Sie haben eine Datei -aus einem früheren Experiment aufgerufen , in diesem Fall erhalten Sie die falschen Ergebnisse ...)
mr .spuratic

1
@ mr.spuratic. Ja, und BSDs, IRIX und zsh stats erkennen es auch nicht. Mit zsh und IRIX stat können Sie stat -f0stattdessen verwenden.
Stéphane Chazelas

9

Alle diese Antworten erscheinen mir mehr oder weniger als Skripte. Für eine Lösung, die wirklich kein Scripting erfordert und davon ausgeht, dass Sie an der Tastatur sitzen und in eine Shell tippen, können Sie Folgendes versuchen:

stat somefile Enter
file Esc_   Enter

Drücken Sie in bash, zsh und ksh mindestens im vi- und im emacs-Modus die Escape-Taste und dann den Unterstrich, um das letzte Wort der vorherigen Zeile in den Bearbeitungspuffer der aktuellen Zeile einzufügen.

Alternativ können Sie die Verlaufssubstitution verwenden:

stat somefile Enter
file! $Enter

In bash werden zsh, csh, tcsh und die meisten anderen Shells !$durch das letzte Wort der vorherigen Befehlszeile ersetzt, wenn die aktuelle Befehlszeile analysiert wird.


Welche anderen Optionen stehen in zsh / bash zur Verfügung Esc _? Können Sie bitte mit der offiziellen Dokumentation verlinken?
Abhijeet Rastogi


1
zsh: linux.die.net/man/1/zshzle und suche nach "insert-last-word"
godlygeek

1
@shadyabhi ^ Links für die Standard-Tastenkombinationen von bash und zsh. Beachten Sie, dass bash nur readline verwendet, sodass (die meisten) bash-Befehle in jeder Anwendung funktionieren, die die readline-Bibliothek verwendet.
Godlygeek

3

Die Art und Weise, wie ich dies vernünftigerweise gefunden habe, ist die Verwendung von xargs, es nimmt eine Datei / Pipe und konvertiert deren Inhalt in Programmargumente. Dies kann mit tee kombiniert werden, das einen Stream aufteilt und an zwei oder mehr Programme sendet. In Ihrem Fall benötigen Sie:

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

Im Gegensatz zu vielen anderen Antworten funktioniert dies unter Linux in bash und den meisten anderen Shells. Ich werde vorschlagen, dass dies ein guter Anwendungsfall für eine Variable ist, aber wenn Sie absolut keinen verwenden können, ist dies der beste Weg, dies nur mit Pipes und einfachen Dienstprogrammen zu tun (vorausgesetzt, Sie haben keine besonders ausgefallene Shell).

Zusätzlich können Sie dies für eine beliebige Anzahl von Programmen tun, indem Sie diese einfach auf die gleiche Weise wie diese beiden nach dem Tee hinzufügen.

BEARBEITEN

Wie in den Kommentaren angedeutet, weist diese Antwort einige Mängel auf. Der erste ist das Potenzial für Interlacing bei der Ausgabe. Dies kann folgendermaßen behoben werden:

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

Dadurch werden die Befehle der Reihe nach ausgeführt und die Ausgabe wird niemals interlaced.

Das zweite Problem ist die Vermeidung von Prozessersetzungen, die in einigen Shells nicht verfügbar sind. Dies kann durch die Verwendung von tpipe anstelle von tee erreicht werden:

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

Dies sollte auf fast jede Shell portierbar sein (ich hoffe) und löst die Probleme in der anderen Empfehlung. Ich schreibe jedoch die letzte aus dem Speicher, da auf meinem aktuellen System keine tpipe verfügbar ist.


1
Die Prozessersetzung ist eine ksh-Funktion, die nur in ksh(nicht in den gemeinfreien Varianten) bashund funktioniert zsh. Beachten Sie, dass statund filegleichzeitig ausgeführt werden, sodass möglicherweise die Ausgabe ineinander verschränkt wird (ich nehme an, Sie verwenden | cat, um die Ausgabepufferung zu erzwingen, um diesen Effekt zu begrenzen?).
Stéphane Chazelas

@ StéphaneChazelas Du hast Recht mit Katze, wenn ich sie benutze. Es ist mir nie gelungen, einen Fall von Interlaced-Eingabe zu produzieren, wenn ich sie benutze. Ich werde jedoch vorübergehend versuchen, eine tragbarere Lösung zu entwickeln.
Vality

3

Diese Antwort ähnelt einigen anderen:

Stat Dateiname   && Datei! #: 1

Im Gegensatz zu den anderen Antworten, bei denen das letzte Argument des vorherigen Befehls automatisch wiederholt wird , müssen Sie für dieses Argument Folgendes zählen:

ls -ld Dateiname   && Datei! #: 2

Im Gegensatz zu einigen anderen Antworten sind hierfür keine Anführungszeichen erforderlich:

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

oder

echo Sometimes a '$cigar' is just a !#:3.

2

Dies ähnelt einigen anderen Antworten, ist jedoch portabler als diese und viel einfacher als diese :

sh -c 'stat "$ 0"; Datei "$ 0" Dateiname

oder

sh -c 'stat "$ 1"; Datei "$ 1" 'sh Dateiname

in dem shzugeordnet ist $0. Obwohl die Eingabe aus drei weiteren Zeichen besteht, ist diese (zweite) Form vorzuziehen, da dies $0der Name ist, den Sie dem Inline-Skript geben. Es wird zum Beispiel in Fehlermeldungen der Shell für dieses Skript verwendet, zum Beispiel wenn fileoder statnicht gefunden oder aus irgendeinem Grund nicht ausgeführt werden kann.


Wenn du willst

stat Dateiname 1  Dateiname 2  Dateiname 3 …; Datei Dateiname 1  Dateiname 2  Dateiname 3

für eine beliebige Anzahl von Dateien tun

sh -c 'stat "$ @"; Datei "$ @" 'sh Dateiname 1  Dateiname 2  Dateiname 3

was funktioniert, weil "$@"es äquivalent zu ist "$1" "$2" "$3" ….


Das $0ist der Name, den Sie diesem Inline-Skript geben möchten. Es wird beispielsweise in Fehlermeldungen der Shell für dieses Skript verwendet. Zum Beispiel, wenn fileoder statnicht gefunden oder aus irgendeinem Grund nicht ausgeführt werden kann. Also, Sie wollen hier etwas Sinnvolles. Wenn Sie nicht begeistert sind, verwenden Sie einfach sh.
Stéphane Chazelas

1

Ich denke, Sie stellen eine falsche Frage, zumindest für das, was Sie versuchen, zu tun. statund fileBefehle nehmen Parameter an, die der Name einer Datei und nicht der Inhalt davon sind. Der Befehl liest später die Datei, die durch den von Ihnen angegebenen Namen gekennzeichnet ist.

Eine Pipe sollte zwei Enden haben, von denen eines als Eingabe und eines als Ausgabe verwendet wird. Dies ist sinnvoll, da Sie möglicherweise verschiedene Bearbeitungen mit verschiedenen Werkzeugen ausführen müssen, bis Sie das gewünschte Ergebnis erhalten. Zu sagen, dass Sie wissen, wie man Ausgänge leitet, aber nicht, wie man Eingänge leitet, ist im Prinzip nicht korrekt.

In Ihrem Fall benötigen Sie überhaupt keine Pipe, da Sie die Eingabe in Form eines Dateinamens vornehmen müssen. Und selbst wenn diese Werkzeuge ( statund file) stdin lesen würden, ist pipe hier wieder nicht relevant, da die Eingabe von keinem der Werkzeuge geändert werden sollte, wenn es zum zweiten kommt.


1

Dies sollte für alle Dateinamen im aktuellen Verzeichnis funktionieren.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *

1
Vorausgesetzt, die Dateinamen enthalten keine doppelten Anführungszeichen, Backslash, Dollar, Backtick, }... und beginnen nicht mit -. Einige printfImplementierungen (zsh, bash, ksh93) haben a %q. Mit zsh, das könnte sein:printf 'stat %q; file %1$q\n' ./* | zsh
Stéphane Chazelas

@StephaneChezales - all das ist jetzt behoben, mit Ausnahme der Möglichkeit der Hardquote. Ich schätze, ich könnte das mit einer einzigen umgehen sed s///, aber es geht um den Umfang - und wenn die Leute das in ihre Dateinamen setzen, sollten sie Windows verwenden.
mikeserv

@ StéphaneChazelas - vergiss es - ich habe vergessen, wie einfach diese zu handhaben waren.
mikeserv

Genau genommen ist dies keine Antwort auf die Frage: „Wie können Sie beide Befehle an derselben Eingabe ausführen ( ohne die Eingabe zweimal zu tippen ... )?“ (Hervorhebung hinzugefügt). Ihre Antwort gilt für alle Dateien im aktuellen Verzeichnis - und sie enthält *zwei . Du könntest es genauso gut sagen stat -- *; file -- *.
Scott

@Scott - die Eingabe wird nicht zweimal eingegeben - *wird erweitert. Die Erweiterung von *plus den beiden Befehlen für jedes Argument in * ist die Eingabe. Sehen? printf commands\ %s\;shift *
MikeServ

1

Es gibt hier ein paar verschiedene Verweise auf "Eingabe", daher werde ich zunächst einige Szenarien vorstellen, um dies zu verstehen. Für Ihre schnelle Antwort auf die Frage in kürzester Form :

stat testfile < <($1)> outputfile

Der obige Befehl führt eine Statistik für die Testdatei durch, nimmt das STDOUT (leitet es um) und fügt es in die nächste Sonderfunktion (den <() -Teil) ein und gibt dann die Endergebnisse dessen, was auch immer das war, in eine neue Datei (Ausgabedatei) aus. Die Datei wird aufgerufen und dann mit den eingebauten Bash-Funktionen referenziert (jeweils $ 1 danach, bis Sie einen neuen Befehlssatz beginnen).

Ihre Frage ist großartig und es gibt verschiedene Antworten und Möglichkeiten, dies zu tun, aber es ändert sich tatsächlich mit dem, was Sie speziell tun.

Zum Beispiel können Sie das auch in einer Schleife abspielen, was sehr praktisch ist. Eine gebräuchliche Verwendung in der Pseudocode-Denkweise ist:

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

Indem Sie das berücksichtigen und dieses Wissen erweitern, können Sie beispielsweise Folgendes erstellen:

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)

Dies führt ein einfaches ls -A in Ihrem aktuellen Verzeichnis aus und teilt dann mit, während jedes Ergebnis von ls -A bis (und hier ist es schwierig!) Grep "thatword" in jedem dieser Ergebnisse durchlaufen und führt nur das vorherige aus printf (in rot), wenn tatsächlich eine Datei mit dem Zusatz "thatword" gefunden wurde. Außerdem werden die Ergebnisse des grep in einer neuen Textdatei protokolliert, files_that_matched_thatword.

Beispielausgabe:

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

All dies druckte einfach das Ergebnis von ls-A aus, nichts Besonderes. Füge etwas hinzu, damit es diesmal grept:

echo "thatword" >> newfile

Führen Sie es jetzt erneut aus:

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

Vielleicht ist die Antwort anstrengender, als Sie derzeit suchen, aber ich glaube, dass Sie bei zukünftigen Bemühungen viel mehr davon profitieren werden, wenn Sie solche Notizen in der Hand halten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.