Warum nicht "welches" verwenden? Was ist dann zu verwenden?


328

Wenn für den Pfad zu einem ausführbaren suchen oder prüfen , was passieren würde , wenn Sie einen Befehlsnamen in einem Unix - Shell geben, gibt es eine Fülle von verschiedenen Dienstprogramme ( which, type, command, whence, where, whereis, whatis, hash, etc.).

Wir hören oft, dass whichdies vermieden werden sollte. Warum? Was sollen wir stattdessen verwenden?


3
Ich denke, die meisten Argumente gegen die Verwendung whichgehen von einem interaktiven Shell-Kontext aus. Diese Frage ist markiert / Portabilität. Daher interpretiere ich die Frage in diesem Zusammenhang als "Was soll man verwenden, anstatt whichdie erste ausführbare Datei eines bestimmten Namens in der zu finden $PATH". Die meisten Antworten und Gründe gegen den whichUmgang mit Aliasen, Builtins und Funktionen, die in den meisten realen portablen Shell-Skripten nur von akademischem Interesse sind. Lokal definierte Aliase werden beim Ausführen eines Shell-Skripts nicht vererbt (es sei denn, Sie geben sie als Quelle an .).
MattBianco

5
@MattBianco, ja, csh(und whichist cshbei den meisten kommerziellen Unices immer noch ein Skript) liest, ~/.cshrcwenn es nicht interaktiv ist. Aus diesem Grund werden Sie feststellen, dass csh-Skripte normalerweise mit beginnen #! /bin/csh -f. whichnicht , weil es zielt darauf ab , Ihnen die Aliasnamen zu geben, weil es als Werkzeug für die (interaktive) Nutzer gemeint ist csh. POSIX-Shells haben Benutzer command -v.
Stéphane Chazelas

@rudimeier, dann wäre die Antwort immer, es sei denn, deine Shell ist (t)csh(oder es macht dir nichts aus, wenn sie dir nicht das richtige Ergebnis liefert), benutze typeoder command -vstattdessen . Siehe die Antworten für warum .
Stéphane Chazelas

1
@rudimeier, ( stat $(which ls)ist aus mehreren Gründen falsch (fehlende --, fehlende Anführungszeichen), nicht nur die Verwendung von which). Du würdest benutzen stat -- "$(command -v ls)". lsDies setzt voraus, dass es sich in der Tat um einen Befehl handelt, der im Dateisystem vorhanden ist (kein in Ihre Shell integrierter Befehl oder eine Aliasfunktion). whichMöglicherweise geben Sie den falschen Pfad an (nicht den Pfad, den Ihre Shell ausführen würde, wenn Sie eingeben ls) oder geben Ihnen einen Alias, wie in der Konfiguration einiger anderer Shells definiert ...
Stéphane Chazelas

1
@rudimeier, wieder gibt es eine Reihe von Bedingungen, unter denen viele whichImplementierungen nicht einmal das liefern lswürden, was durch ein Nachschlagen von gefunden werden würde $PATH(unabhängig davon, was lsin Ihrer Shell aufgerufen werden könnte). sh -c 'command -v ls'oder zsh -c 'rpm -q --whatprovides =ls'eher die richtige Antwort geben. Der Punkt hier ist, dass whichein gebrochenes Erbe aus csh.
Stéphane Chazelas

Antworten:


366

Hier ist alles, was Sie nie gedacht hätten, dass Sie jemals davon nichts wissen wollen:

Zusammenfassung

So rufen Sie den Pfadnamen einer ausführbaren Datei in einem Bourne-ähnlichen Shell-Skript ab (es gibt einige Vorsichtsmaßnahmen; siehe unten):

ls=$(command -v ls)

So stellen Sie fest, ob ein bestimmter Befehl vorhanden ist:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

An der Eingabeaufforderung einer interaktiven Bourne-ähnlichen Shell:

type ls

Der whichBefehl ist ein zerbrochenes Erbe der C-Shell und sollte besser in Bourne-ähnlichen Shells allein gelassen werden.

Anwendungsfälle

Es gibt einen Unterschied, ob Sie diese Informationen als Teil eines Skripts oder interaktiv an der Shell-Eingabeaufforderung suchen.

An der Shell-Eingabeaufforderung lautet der typische Anwendungsfall: Dieser Befehl verhält sich merkwürdig. Verwende ich den richtigen? Was genau ist passiert, als ich getippt habe mycmd? Kann ich mir genauer ansehen, was es ist?

In diesem Fall möchten Sie wissen, was Ihre Shell tut, wenn Sie den Befehl aufrufen, ohne ihn tatsächlich aufzurufen.

In Shell-Skripten sieht es meistens ganz anders aus. In einem Shell-Skript gibt es keinen Grund, warum Sie wissen möchten, wo oder was ein Befehl ist, wenn Sie ihn nur ausführen möchten. Im Allgemeinen möchten Sie den Pfad der ausführbaren Datei kennen, damit Sie mehr Informationen daraus abrufen können (z. B. den Pfad zu einer anderen Datei im Verhältnis dazu oder Informationen aus dem Inhalt der ausführbaren Datei unter diesem Pfad lesen).

Interaktiv, können Sie darüber wissen alle die my-cmdBefehle auf dem System, in Skripten, selten so.

Die meisten verfügbaren Tools wurden (wie so oft) für die interaktive Verwendung entwickelt.

Geschichte

Ein bisschen Geschichte zuerst.

Die frühen Unix-Shells hatten bis Ende der 70er Jahre keine Funktionen oder Aliase. Nur das traditionelle Nachschlagen von ausführbaren Dateien in $PATH. csheingeführt Aliase um 1978 (obwohl cshwurde zuerst veröffentlicht in 2BSD, der im Mai 1979), und auch die Verarbeitung einer .cshrcfür die Benutzer die Schale gestalten (jede Schale, wie cshliest .cshrcauch wenn sie nicht interaktiv wie in Skripten).

Während die Bourne-Shell 1979 zum ersten Mal in Unix V7 veröffentlicht wurde, wurde die Funktionsunterstützung erst viel später hinzugefügt (1984 in SVR2), und es gab sowieso nie eine rcDatei (die .profiledient zum Konfigurieren Ihrer Umgebung, nicht die Shell an sich ).

csh wurde viel populärer als die Bourne-Shell, da sie (obwohl sie eine schrecklich schlechtere Syntax als die Bourne-Shell hatte) viele bequemere und schönere Funktionen für den interaktiven Gebrauch hinzufügte.

In 3BSD(1980), ein whichcsh - Skript wurde für die hinzugefügten cshBenutzer eine ausführbare Datei zu identifizieren, und es ist ein kaum anderes Skript Sie finden whichauf vielen kommerziellen Unix - Varianten heute (wie Solaris, HP / UX, AIX oder Tru64).

Dieses Skript liest den Benutzer ~/.cshrc(wie alle cshSkripte, sofern sie nicht mit aufgerufen werden csh -f) und sucht die angegebenen Befehlsnamen in der Liste der Aliase und in $path(dem Array, cshdas auf der Grundlage verwaltet wird $PATH).

Los geht's, whichkam als erstes für die damals beliebteste Shell (und cshwar bis Mitte der 90er Jahre immer noch beliebt), was der Hauptgrund ist, warum sie in Büchern dokumentiert wurde und immer noch weit verbreitet ist.

Beachten Sie csh, dass dieses whichcsh-Skript auch für einen Benutzer nicht unbedingt die richtigen Informationen enthält. Es werden die in definierten Aliase abgerufen ~/.cshrc, nicht die Aliase , die Sie möglicherweise später an der Eingabeaufforderung oder beispielsweise durch die sourceEingabe einer anderen cshDatei definiert haben, und (obwohl dies keine gute Idee wäre) PATHmöglicherweise in neu definiert ~/.cshrc.

Wenn Sie diesen whichBefehl über eine Bourne-Shell ausführen, werden die in Ihrer definierten Aliase weiterhin gesucht. ~/.cshrcWenn Sie jedoch keinen haben, weil Sie ihn nicht verwenden csh, erhalten Sie wahrscheinlich immer noch die richtige Antwort.

Eine ähnliche Funktionalität wurde der Bourne-Shell erst 1984 in SVR2 mit dem typeeingebauten Befehl hinzugefügt. Die Tatsache, dass es (im Gegensatz zu einem externen Skript) eingebaut ist, bedeutet, dass es Ihnen (in gewissem Umfang) die richtigen Informationen geben kann , da es Zugriff auf die Interna der Shell hat.

Der ursprüngliche typeBefehl hatte ein ähnliches Problem wie das whichSkript, da er keinen Fehlerbeendigungsstatus zurückgab, wenn der Befehl nicht gefunden wurde. Im Gegensatz dazu wird für ausführbare Dateien nicht nur das whichausgegeben, was die Verwendung in Skripten erschwert.ls is /bin/ls/bin/ls

Die Bourne-Shell von Unix Version 8 (nicht im typeUmlauf) wurde umbenannt whatis. Und die Plan9-Shell rc( der ehemalige Nachfolger von Unix) (und ihre Derivate wie akangaund es) haben es whatisebenfalls.

Die Korn-Shell (eine Teilmenge, auf der die POSIX-sh-Definition basiert), die Mitte der 80er Jahre entwickelt wurde, aber vor 1988 nicht allgemein verfügbar war, fügte viele der cshFunktionen (Zeileneditor, Aliase ...) der Bourne-Shell hinzu . Es wurde whence(zusätzlich zu type) ein eigenes eingebautes Programm hinzugefügt , das mehrere Optionen -vzur Verfügung stellte ( um die typeähnlich ausführliche Ausgabe bereitzustellen und -pnur nach ausführbaren Dateien zu suchen (keine Aliase / Funktionen ...)).

In den späten 80er Jahren, Anfang der 90er Jahre, kamen einige freie Software- Shell-Implementierungen heraus , die mit den Turbulenzen in Bezug auf die Urheberrechtsprobleme zwischen AT & T und Berkeley einhergingen. Die gesamte Almquist-Shell (ash, die die Bourne-Shell in BSDs ersetzen soll), die gemeinfreie Implementierung von ksh (pdksh) bash(gesponsert von der FSF), zshwurde zwischen 1989 und 1991 veröffentlicht.

Obwohl Ash als Ersatz für die Bourne-Shell typegedacht war, wurde es erst viel später (in NetBSD 1.3 und FreeBSD 2.3) eingebaut hash -v. OSF / 1 /bin/shhatte eine typeeingebaute Version, die immer 0 bis OSF / 1 v3.x zurückgab. bashfügte kein hinzu whence, fügte aber eine -pOption hinzu type, um den Pfad auszudrucken ( type -pwürde lauten whence -p) und alle übereinstimmenden Befehle -azu melden . made builtin und fügte einen Befehl hinzu , der sich wie 's verhält . hat sie alle.tcshwhichwherebashtype -azsh

In der fishShell (2005) ist ein typeBefehl als Funktion implementiert.

Das whichcsh-Skript wurde in der Zwischenzeit aus NetBSD entfernt (da es in tcsh eingebaut und in anderen Shells nicht sehr nützlich ist) und die Funktionalität wurde hinzugefügt whereis(wenn es als aufgerufen wird which, whereisverhält es sich so, whichdass es nur ausführbare Dateien in sucht $PATH). In OpenBSD und FreeBSD whichwurde auch eine in C geschriebene geändert, die $PATHnur Befehle in sucht .

Implementierungen

Es gibt Dutzende von Implementierungen eines whichBefehls auf verschiedenen Unices mit unterschiedlicher Syntax und unterschiedlichem Verhalten.

Unter Linux (neben den in tcshund eingebauten zsh) gibt es mehrere Implementierungen. Auf neueren Debian-Systemen ist es beispielsweise ein einfaches POSIX-Shell-Skript, das nach Befehlen in sucht $PATH.

busyboxhat auch einen whichBefehl.

Es gibt eine, GNU whichdie wahrscheinlich die extravaganteste ist. Es versucht, das, was das whichcsh-Skript getan hat, auf andere Shells auszudehnen : Sie können sagen, was Ihre Aliase und Funktionen sind, damit Sie eine bessere Antwort erhalten (und ich glaube, dass einige Linux-Distributionen einige globale Aliase setzen, um dies bashzu tun). .

zshEs gibt einige Operatoren , die auf den Pfad der ausführbaren Dateien erweitert werden müssen: den = Dateinamenerweiterungsoperator und den :cÄnderungswert für die Verlaufserweiterung (hier angewendet auf die Parametererweiterung ):

$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls

zshzsh/parametersmacht im Modul auch die Kommando-Hash-Tabelle zum commandsassoziativen Array:

$ print -r -- $commands[ls]
/bin/ls

Das whatisHilfsprogramm (mit Ausnahme des Hilfsprogramms in der Unix V8 Bourne-Shell oder Plan 9 rc/ es) ist nicht wirklich verwandt, da es nur zur Dokumentation dient (greift auf die whatis-Datenbank zu, dh auf die Manpage-Übersicht).

whereisWurde auch 3BSDzur gleichen Zeit hinzugefügt, als whichwäre es in geschrieben C, nicht cshund wird verwendet, um zur gleichen Zeit die ausführbare Datei, die Manpage und den Quellcode zu suchen, aber nicht basierend auf der aktuellen Umgebung. Das entspricht einem anderen Bedürfnis.

An der Standardfront gibt POSIX nun die Befehle command -vund an -V(die bis POSIX.2008 optional waren). UNIX gibt den typeBefehl an (keine Option). Das ist alles ( where, which, whencesind in keiner Norm festgelegt)

Bis zu einer bestimmten Version typeund command -voptional in der Linux Standard Base-Spezifikation, was erklärt, warum zum Beispiel einige alte Versionen von posh(obwohl auf pdkshdenen beide basierten ) keine hatten. command -vwurde auch zu einigen Bourne-Shell-Implementierungen hinzugefügt (wie unter Solaris).

Status Heute

Heutzutage ist dies der Status typeund er command -vist in allen Bourne-ähnlichen Shells allgegenwärtig (beachten Sie jedoch, wie von @jarno bemerkt, die Einschränkung / den Fehler, bashwenn Sie sich nicht im POSIX-Modus befinden, oder einige Nachkommen der Almquist-Shell, die in den Kommentaren unten aufgeführt sind). tcshist die einzige Shell, die Sie verwenden möchten which(da keine typevorhanden und whicheingebaut ist).

In den Schalen ausgenommen tcshund zsh, whichman kann sagen , den Pfad der gegebenen ausführbaren solange es keine Alias oder eine Funktion von demselben Namen in einem unserer ~/.cshrc, ~/.bashrcoder jede Shell - Startdatei und Sie definieren nicht $PATHin Ihrem ~/.cshrc. Wenn Sie einen Alias ​​oder eine Funktion dafür definiert haben, kann er oder sie Ihnen davon erzählen, oder sie können Ihnen das Falsche sagen.

Wenn Sie über alle Befehle unter einem bestimmten Namen informiert werden möchten, gibt es nichts Tragbares. Sie würden wherein tcshoder zsh, type -ain bashoder zsh, whence -ain ksh93 und in anderen Shells verwenden, die Sie typein Kombination mit which -adenen verwenden können, die möglicherweise funktionieren.

Empfehlungen

Abrufen des Pfadnamens zu einer ausführbaren Datei

Um den Pfadnamen einer ausführbaren Datei in einem Skript zu ermitteln, gibt es einige Vorsichtsmaßnahmen:

ls=$(command -v ls)

wäre der Standard Weg, um es zu tun.

Es gibt jedoch einige Probleme:

  • Es ist nicht möglich, den Pfad der ausführbaren Datei zu kennen, ohne sie auszuführen. Alle type, which, command -v... alle Heuristik verwendet den Weg , um herauszufinden. Sie durchlaufen die $PATHKomponenten und suchen die erste Nicht-Verzeichnis-Datei, für die Sie die Berechtigung zum Ausführen haben. Abhängig von der Shell führen viele von ihnen (Bourne, AT & T ksh, zsh, ash ...) die Ausführung in der Reihenfolge aus, $PATHbis der execveSystemaufruf nicht mehr mit einem Fehler zurückkehrt . Zum Beispiel, wenn $PATHenthält /foo:/barund Sie ausführen möchten ls, werden sie zuerst versuchen, auszuführen, /foo/lsoder wenn dies fehlschlägt /bar/ls. Jetzt Ausführung von/foo/lskann fehlschlagen, weil Sie keine Ausführungsberechtigung haben, aber auch aus vielen anderen Gründen, zum Beispiel, weil es keine gültige ausführbare Datei ist. command -v lswürde melden, /foo/lsob Sie über die Ausführungsberechtigung für verfügen /foo/ls, aber lsmöglicherweise tatsächlich ausgeführt werden, /bar/lswenn /foo/lskeine gültige ausführbare Datei vorhanden ist.
  • Wenn fooes sich um eine integrierte Funktion oder einen Alias ​​handelt, wird command -v foozurückgegeben foo. Mit einigen Muscheln mögen ash, pdkshoder zshkann es auch zurückkehren , foowenn $PATHdie leere Zeichenfolge enthält und es gibt eine ausführbare fooDatei im aktuellen Verzeichnis. Unter bestimmten Umständen müssen Sie dies möglicherweise berücksichtigen. Denken Sie zum Beispiel daran, dass die Liste der integrierten Funktionen von der Shell-Implementierung abhängt (zum Beispiel mountwird sie manchmal für busybox integriert sh) und bashFunktionen aus der Umgebung abrufen kann.
  • Wenn $PATHrelative Pfadkomponenten enthalten sind (normalerweise .oder die leere Zeichenfolge, die sich beide auf das aktuelle Verzeichnis beziehen, aber alles command -v cmdMögliche sein können), wird je nach Shell möglicherweise kein absoluter Pfad ausgegeben. Daher ist der Pfad, den Sie zum Zeitpunkt Ihres Laufs erhalten command -v, nicht mehr gültig, nachdem Sie sich an einem cdanderen Ort befinden.
  • Anekdotische: mit der ksh93 Schale, wenn /opt/ast/bin(obwohl das genaue Pfad auf verschiedenen Systemen unterschiedlich sein kann glaube ich) in dir ist $PATH, wird ksh93 zur Verfügung , ein paar zusätzliche builtins machen ( chmod, cmp, cat...), aber command -v chmodwird zurückkehren , /opt/ast/bin/chmodauch wenn dieser Weg doesn‘ existiert nicht.

Bestimmen, ob ein Befehl vorhanden ist

Um herauszufinden, ob ein bestimmter Befehl standardmäßig vorhanden ist, können Sie Folgendes tun:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

Wo könnte man verwenden wollen which

(t)csh

In cshund tcshhast du nicht viel Auswahl. In tcsh, das ist in Ordnung, wie whiches eingebaut ist. Dies cshist der Systembefehl which, der in einigen Fällen möglicherweise nicht die gewünschten Aktionen ausführt.

finde Befehle nur in einigen Shells

Ein Fall , in dem es könnte sinnvoll nutzen machen which, wenn Sie den Pfad einen Befehl wissen wollen, in potenziell Shell builtins oder Funktionen zu ignorieren bash, csh(nicht tcsh), dashoder BourneShell - Scripts, also Schalen, die nicht über whence -p(wie kshoder zsh) , command -ev(like yash), whatis -p( rc, akanga) oder ein eingebautes which(like tcshoder zsh) auf Systemen, wo whichverfügbar ist und nicht das cshSkript.

Wenn diese Bedingungen erfüllt sind, dann:

echo=$(which echo)

würde dir den Pfad des Ersten echoin $PATH(außer in echoEckfällen ) geben, unabhängig davon, ob es sich auch um eine eingebaute / alias / Funktion handelt oder nicht.

In anderen Shells bevorzugen Sie:

  • zsh : echo==echooder echo=$commands[echo]oderecho=${${:-echo}:c}
  • ksh , zsh :echo=$(whence -p echo)
  • Yash :echo=$(command -ev echo)
  • rc , akanga : echo=`whatis -p echo`(Vorsicht vor Pfaden mit Leerzeichen)
  • Fisch :set echo (type -fp echo)

Beachten Sie, dass Sie, wenn Sie nur diesen echoBefehl ausführen möchten , den Pfad nicht abrufen müssen, sondern einfach Folgendes tun können:

env echo this is not echoed by the builtin echo

Zum Beispiel mit tcsh, um zu verhindern, dass das eingebaute Element whichverwendet wird:

set Echo = "`env which echo`"

wenn Sie einen externen Befehl benötigen

Ein weiterer Fall, den Sie möglicherweise verwenden möchten, whichist, wenn Sie tatsächlich einen externen Befehl benötigen . POSIX setzt voraus, dass alle Shell-Builtins (wie command) auch als externe Befehle verfügbar sind, was commandauf vielen Systemen leider nicht der Fall ist. Beispielsweise ist es selten, commandunter Linux-basierten Betriebssystemen einen Befehl zu finden , während die meisten von ihnen einen whichBefehl haben (wenn auch verschiedene mit unterschiedlichen Optionen und Verhaltensweisen).

Fälle, in denen Sie einen externen Befehl benötigen, sind alle Fälle, in denen Sie einen Befehl ausführen, ohne eine POSIX-Shell aufzurufen.

Die system("some command line"), popen()... -Funktionen von C oder verschiedenen Sprachen rufen eine Shell auf, um diese Befehlszeile zu analysieren. system("command -v my-cmd")Arbeiten Sie also mit ihnen. Eine Ausnahme wäre, perldie die Shell optimiert, wenn sie kein Shell-Sonderzeichen (außer Leerzeichen) sieht. Das gilt auch für seinen Backtick-Betreiber:

$ perl -le 'print system "command -v emacs"'
-1
$ perl -le 'print system ":;command -v emacs"'
/usr/bin/emacs
0

$ perl -e 'print `command -v emacs`'
$ perl -e 'print `:;command -v emacs`'
/usr/bin/emacs

Die Hinzufügung des :;oben perlGesagten zwingt dazu, dort eine Shell aufzurufen. Wenn Sie verwenden which, müssten Sie diesen Trick nicht anwenden.


24
@ Joe, whichist ein cshSkript für viele kommerzielle Unices. Der Grund ist historisch, deshalb habe ich die Geschichte angegeben, damit die Leute verstehen, woher sie stammt, warum die Leute sich daran gewöhnt haben und warum es eigentlich keinen Grund gibt, warum Sie sie verwenden sollten. Und ja, manche Leute benutzen (t) csh. Noch verwendet nicht jeder Linux
Stéphane Chazelas

12
Nachdem ich diesen Beitrag gelesen habe, habe ich eine Menge Kontext für die Antwort gefunden, aber nicht die Antwort selbst. Wo in diesem Beitrag sieht es eigentlich sagen , warum nicht zu verwenden which, wie die Dinge im Gegensatz Sie verwenden könnten versuchen , whichzu tun, die Geschichte which, Implementierungen which, anderer Befehle bezogene Aufgaben zu tun, oder Gründe tatsächlich zu nutzen which? Warum sind die anderen Befehle besser ? Was machen sie anders als which? Wie vermeiden sie seine Fallstricke? Diese Antwort gibt tatsächlich mehr Worte über die Probleme mit den Alternativen aus als über die Probleme mit which.
User62251

1
Im Gegensatz zu dem, was die Antwort behauptet, command -vwird die Ausführungsberechtigung nicht überprüft, zumindest wenn Sie sie mit einem reinen Dateinamenargument ohne Pfad aufrufen. Ich habe mit Dash 0.5.8 und GNU Bash 4.3.48 getestet.
jarno

2
@ StéphaneChazelas Wenn ich eine neue Datei erstelle touch /usr/bin/mytestfileund danach starte command -v mytestfile, wird der Pfad angegeben (wohingegen which mytestfiledies nicht der Fall ist).
jarno

2
@jarno, oh ja, du hast recht. bashwird sich für eine nicht ausführbare Datei entscheiden, wenn sie keine ausführbare Datei finden kann, also ist es "OK" (obwohl in der Praxis eher ein command -v/ typeFehler zurückgegeben wird), da dies der Befehl ist, den sie ausführen würde, wenn Sie ausführen mytestfile, aber die dashVerhalten ist fehlerhaft, als ob es eine nicht ausführbare Datei cmdvor einer ausführbaren gibt, command -vgibt die nicht ausführbare Datei zurück, während cmddie ausführbare Datei ausgeführt wird (die falsche Datei ist ebenfalls gehasht). FreeBSD sh(auch basierend auf ash) hat den gleichen Fehler. zsh, yash, ksh, mksh, bash wie sh sind OK.
Stéphane Chazelas

47

Die Gründe, warum man nicht verwenden möchte, whichwurden bereits erläutert, aber hier sind einige Beispiele für einige Systeme, bei denen whichtatsächlich ein Fehler auftritt.

Bei Bourne-ähnlichen Shells wird die Ausgabe von whichmit der Ausgabe von verglichen type(da typees sich um eine eingebaute Shell handelt, handelt es sich um die grundlegende Wahrheit, da sie uns sagt, wie ein Befehl aufgerufen werden würde).

Viele Fälle sind Ecke Fälle, aber bedenken Sie, dass which/ typehäufig in der Ecke Fällen verwendet werden (um die Antwort auf ein unerwartetes Verhalten zu finden wie: ? Warum auf der Erde ist , dass Befehl so zu verhalten, die eine bin ich Aufruf ).

Die meisten Systeme, die meisten Bourne-ähnlichen Shells: Funktionen

Der naheliegendste Fall betrifft Funktionen:

$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls

Der Grund dafür ist, dass whichnur Berichte über ausführbare Dateien und manchmal über Aliase (obwohl nicht immer die Ihrer Shell) erstellt werden und nicht über Funktionen.

Die GNU welche Manpage hat ein kaputtes (da sie vergessen haben zu zitieren $@) Beispiel, wie man es benutzt, um auch Funktionen zu melden, aber genau wie bei Aliases, weil es keinen Shell Syntax Parser implementiert, ist es leicht zu täuschen:

$ which() { (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@";}
$ f() { echo $'\n}\ng ()\n{ echo bar;\n}\n' >> ~/foo; }
$ type f
f is a function
f ()
{
echo '
}
g ()
{ echo bar;
}
' >> ~/foo
}
$ type g
bash: type: g: not found
$ which f
f ()
{
echo '
}
$ which g
g ()
{ echo bar;
}

Die meisten Systeme, die meisten Bourne-ähnlichen Shells: Builtins

Ein weiterer offensichtlicher Fall ist builtins oder Keywords, als whichsein ein externer Befehl hat keine Möglichkeit zu wissen , welche builtins Shell (und einige Granaten wie zsh, bashoder kshkann builtins dynamisch laden):

$ type echo . time
echo is a shell builtin
. is a shell builtin
time is a shell keyword
$ which echo . time
/bin/echo
which: no . in (/bin:/usr/bin)
/usr/bin/time

(das gilt nicht für zshwo whichwird builtin)

Solaris 10, AIX 7.1, HP / UX 11i, Tru64 5.1 und viele andere:

$ csh
% which ls
ls:   aliased to ls -F
% unalias ls
% which ls
ls:   aliased to ls -F
% ksh
$ which ls
ls:   aliased to ls -F
$ type ls
ls is a tracked alias for /usr/bin/ls

Das liegt daran, dass auf den meisten kommerziellen Unices which(wie in der ursprünglichen Implementierung auf 3BSD) ein cshSkript zum Lesen vorhanden ist ~/.cshrc. Die Aliasnamen, die gemeldet werden, sind diejenigen, die dort definiert sind, unabhängig von den aktuell von Ihnen definierten Aliasnamen und unabhängig von der Shell, die Sie tatsächlich verwenden.

In HP / UX oder Tru64:

% echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
% setenv PATH ~/bin:/bin:/usr/bin
% ln -s /bin/ls ~/bin/
% which ls
/bin/ls

(Die Solaris- und AIX-Versionen haben dieses Problem behoben, indem sie es $pathvor dem Lesen ~/.cshrcgespeichert und wiederhergestellt haben, bevor die Befehle aufgerufen wurden.)

$ type 'a b'
a b is /home/stephane/bin/a b
$ which 'a b'
no a in /usr/sbin /usr/bin
no b in /usr/sbin /usr/bin

Oder:

$ d="$HOME/my bin"
$ mkdir "$d"; PATH=$PATH:$d
$ ln -s /bin/ls "$d/myls"
$ type myls
myls is /home/stephane/my bin/myls
$ which myls
no myls in /usr/sbin /usr/bin /home/stephane/my bin

(Natürlich cshkönnen Sie nicht erwarten , dass ein Skript mit Argumenten arbeitet, die Leerzeichen enthalten ...)

CentOS 6.4, bash

$ type which
which is aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
$ alias foo=': "|test|"'
$ which foo
alias foo=': "|test|"'
        /usr/bin/test
$ alias $'foo=\nalias bar='
$ unalias bar
-bash: unalias: bar: not found
$ which bar
alias bar='

Auf diesem System gibt es einen systemweit definierten Alias, der den GNU- whichBefehl umschließt .

Die gefälschte Ausgabe ist da whichdie Ausgabe liest bash‚s aliasaber nicht weiß , wie man es richtig zu analysieren und verwendet Heuristiken (ein Alias pro Zeile, sucht der erste Befehl nach einer gefunden |, ;, &...)

Das Schlimmste an CentOS ist, dass zshes einen perfekt whicheingebauten Befehl gibt, aber CentOS hat es geschafft, ihn zu brechen, indem es durch einen nicht funktionierenden Alias ​​für GNU ersetzt wurde which.

Debian 7.0, ksh93:

(gilt jedoch für die meisten Systeme mit vielen Shells)

$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which

Auf Debian /bin/whichist ein /bin/shSkript. In meinem Fall shist , dashaber es ist das gleiche , wenn es bash.

Ein ungesetzt PATHist nicht zu deaktivieren PATHLookup, bedeutet aber das System unter Verwendung von Standard - PATH , die leider auf Debian, niemand stimmt ( dashund bashhabe /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, zshhat /bin:/usr/bin:/usr/ucb:/usr/local/bin, ksh93hat /bin:/usr/bin, mkshhat /usr/bin:/bin( $(getconf PATH)), execvp()(wie in env) hat :/bin:/usr/bin(ja, sieht im aktuellen Verzeichnis zuerst! )).

Welches ist , warum whiches falsch oben bekommt , da es mit dash‚s Standard , PATHdie aus unterschiedlichen ist ksh93‘ s

Es ist nicht besser mit GNU, whichdas berichtet:

which: no which in ((null))

(Interessanterweise gibt es /usr/local/bin/whichauf meinem System tatsächlich ein akangaSkript, das mitgeliefert wurde akanga(ein rcShell-Derivat, in dem die Standardeinstellung PATHlautet /usr/ucb:/usr/bin:/bin:.).

Bash, jedes System:

Derjenige, auf den sich Chris in seiner Antwort bezieht :

$ PATH=$HOME/bin:/bin
$ ls /dev/null
/dev/null
$ cp /bin/ls bin
$ type ls
ls is hashed (/bin/ls)
$ command -v ls
/bin/ls
$ which ls
/home/chazelas/bin/ls

Auch nach hashmanuellem Aufruf :

$ type -a which
which is /usr/local/bin/which
which is /usr/bin/which
which is /bin/which
$ hash -p /bin/which which
$ which which
/usr/local/bin/which
$ type which
which is hashed (/bin/which)

Nun ein Fall, wo whichund manchmal typescheitern:

$ mkdir a b
$ echo '#!/bin/echo' > a/foo
$ echo '#!/' > b/foo
$ chmod +x a/foo b/foo
$ PATH=b:a:$PATH
$ which foo
b/foo
$ type foo
foo is b/foo

Nun mit einigen Muscheln:

$ foo
bash: ./b/foo: /: bad interpreter: Permission denied

Mit anderen:

$ foo
a/foo

Weder können whichnoch typekönnen im Voraus wissen, dass b/foonicht ausgeführt werden kann. Einige Granaten wie bash, kshoder yash, wenn Aufruf foowird in der Tat versuchen zu laufen b/fooeinen Fehler und zu berichten, während andere (wie zsh, ash, csh, Bourne, tcsh) läuft a/fooauf den Ausfall des execve()Systemaufruf auf b/foo.


mkshverwendet tatsächlich etwas anderes für den Standard $PATH: Zuerst wird die Kompilierungszeitkonstante des Betriebssystems _PATH_DEFPATHverwendet (am häufigsten auf BSDs), dann confstr(_CS_PATH, …)wird POSIX verwendet, und wenn beide nicht vorhanden sind oder fehlschlagen, /bin:/usr/bin:/sbin:/usr/sbinwird verwendet.
Mirabilos

1
In Ihrem ersten Beispiel wird, auch wenn lses sich um eine Funktion handelt, diese lsvon PATH verwendet. Und whichist gut zu sagen, welche verwendet wird /usr/bin/ls oder /usr/local/bin/ls. Ich sehe nicht "Warum nicht welche verwenden" ....
rudimeier

@rudimeier, Das which lswird mir geben , /bin/lsunabhängig davon , ob die lsFunktion aufruft /bin/lsoder /opt/gnu/bin/lsoder diroder gar nichts. IOW, which(das, was Implementierungen, IMMV) gibt etwas irrelevantes
Stéphane Chazelas

1
@ StéphaneChazelas. Nein nein Nein. Ich weiß schon, dass mein lseine Funktion ist. Ich weiß , dass meine lsFunktion ruft lsaus PATH. Jetzt whichsag mir wo die Datei ist. Es wird nur ein einziger Anwendungsfall angezeigt: "Was würde meine Shell mit diesem Befehl tun?" Für diesen Anwendungsfall whichist falsch, richtig. Es gibt aber auch andere Anwendungsfälle, bei denen (GNU) whichgenau das Richtige ist.
rudimeier

@rudimeter, hängt von der whichImplementierung ab. Einige werden Ihnen sagen, dass es sich um einen Alias ​​handelt (wenn Sie einen Alias ​​konfiguriert haben oder wenn ~/.cshrcin Ihrem Heim ein solcher Alias ​​vorhanden ist), andere geben Ihnen einen Pfad an, der unter bestimmten Bedingungen jedoch falsch ist. sh -c 'command -v ls'Wenn auch nicht perfekt, ist es dennoch wahrscheinlicher, dass Sie die richtige Antwort auf diese unterschiedliche Anforderung erhalten (und dies ist auch Standard).
Stéphane Chazelas

21

Eine Sache, die Stephane (aus meiner kurzen Sicht) anscheinend nicht erwähnt hat, ist, dass whichsie keine Ahnung von der Pfad-Hash-Tabelle Ihrer Shell hat. Dies hat zur Folge, dass möglicherweise ein Ergebnis zurückgegeben wird, das nicht für das tatsächlich ausgeführte Ergebnis repräsentativ ist, was das Debuggen unwirksam macht.


6

Im Sinne von UNIX: Lassen Sie jedes Programm eine Sache gut machen.

Wenn das Ziel ist zu antworten: Welche ausführbare Datei existiert mit diesem Namen?

Das mit Debian-Systemen gelieferte ausführbare Programm ist eine gute Antwort. Das mit csh mitgelieferte Aliase, das ist eine Quelle von Problemen. Die, die einige Shells als internes Builtin bereitstellen, haben ein anderes Ziel. Verwenden Sie entweder diese ausführbare Datei oder das Skript am Ende dieser Antwort.

Wenn dieses Skript verwendet wird, ist die Antwort sauber, einfach und nützlich.

Dieses Ziel entspricht dem ersten Satz Ihrer Frage:

Auf der Suche nach dem Pfad zu einer ausführbaren Datei……

Wenn Sie ein System, das nicht ein ausführbares hat genannt , die ( die meisten Linux - Systeme haben) Sie eine in erstellen können , ~/bin/whichbevor /bin/in der PATH so persönlich ausführbar Übersteuerungssytem diejenigen wie am Ende der Post:

Diese ausführbare Datei listet (standardmäßig) alle im PATH gefundenen ausführbaren Dateien auf. Wenn nur die erste erforderlich ist, ist die Option -fverfügbar.


An diesem Punkt fallen wir in ein anderes Ziel:

Was die Shell ausführen wird (nach dem Parsen)

Das kommt aus deinem zweiten Satz:

Überprüfen, was passieren würde, wenn Sie einen Befehlsnamen in eine Unix-Shell eingeben

Dieses zweite Thema versucht, eine gute Antwort auf eine Frage zu finden, die ziemlich schwer zu beantworten ist. Muscheln haben unterschiedliche Ansichten, Eckfälle und (zumindest) unterschiedliche Interpretationen. Hinzufügen dazu:

Es gibt eine Vielzahl verschiedener Dienstprogramme (welche, Typ, Befehl, Woher, Woher, Was, Hasch usw.).

Und sicher, alle Versuche stimmen mit diesem Ziel überein.


Vermeiden Sie welche?

Wir hören oft das, was vermieden werden sollte.

Ich frage mich: Warum sollte das gesagt werden, wenn es gut whichfunktioniert (zumindest in Debian)?

Im Sinne von UNIX: Lassen Sie jedes Programm eine Sache gut machen.

Das externe Programm whichmacht eines: Suchen Sie die erste ausführbare Datei auf dem Pfad, die denselben Namen wie der Befehlsname hat . Und macht es einigermaßen gut.

Ich kenne kein anderes Programm oder Dienstprogramm, das diese Frage grundlegender beantwortet. Als solches ist es nützlich und kann bei Bedarf verwendet werden.

Die command -pv commandNamenaheliegendste Alternative scheint zu sein :, aber das wird auch über eingebaute und Aliase berichten. Nicht die gleiche Antwort.

Natürlich whichist es begrenzt, es beantwortet nicht alle Fragen, kein Tool könnte das tun (naja, noch nicht ...). Es ist jedoch nützlich, wenn Sie die Frage beantworten, für deren Beantwortung es entwickelt wurde (die Frage oben). Vieles davon edwurde begrenzt und sederschien dann (oder vi/ vim). Oder wie awkwurde begrenzt und ließ Perl erscheinen und erweitern. Dennoch ed, sedoder / und awkspezifische Anwendungsfälle, wo vimoder perlsind nicht die besten Werkzeuge.

Warum?

Wahrscheinlich, weil whichein Shell-Benutzer nur einen Teil der Frage beantwortet, die er möglicherweise stellt:

Was wird ausgeführt, wenn ich einen Befehlsnamen eingebe?


Externe welche

Welche sollte (in vielen Systemen) als externe ausführbare Datei verfügbar sein.
Der einzige sichere Weg, dieses externe Tool aufzurufen, ist, env zu verwenden, um die Shell zu verlassen und dann aufzurufen which(was in allen Shells funktioniert):

 $ env which which
 /usr/bin/which

Oder verwenden Sie den vollständigen Pfad zu which(der auf verschiedenen Systemen variieren kann):

 /usr/bin/which which 

Warum ist das hacknötig? Weil sich einige Muscheln (speziell zsh) verstecken which:

 $ zsh -c 'which which'
 which: shell built-in command

Ein externes Tool (wie env) zu sein, erklärt perfekt, warum es keine internen Shell-Informationen meldet. Wie Aliase, Funktionen, integrierte Funktionen, spezielle integrierte Funktionen, Shell-Variablen (nicht exportiert) usw.:

 $ env which ls
 /usr/bin/ls
 $ env which ll       # empty output

Die leere Ausgabe von ll(ein allgemeiner Alias ​​für ll='ls -l') zeigt an, dass llkeine Beziehung zu einem ausführbaren Programm besteht, oder zumindest, dass keine ausführbare Datei llim PATH angegeben ist. Die Verwendung von llsollte etwas anderes aufrufen, in diesem Fall einen Alias:

 $ type ll
 ll is aliased to `ls -l'

type und command

Die Befehle typeund command -vwerden von POSIX angefordert. Es ist zu erwarten, dass sie in den meisten Shells funktionieren, mit Ausnahme von csh, tcsh, fish und rc.

Beide Befehle könnten verwendet werden, um einen anderen Gesichtspunkt bereitzustellen, von dem aus der Befehl ausgeführt wird.

whence, where, whereis, whatis,hash

Dann gibt es whence, where, whereis, whatis, hash, und einige andere. Alle unterschiedlichen Antworten auf ähnliche Fragen. Alle arbeiten auf unterschiedliche Weise in verschiedenen Schalen. Wahrscheinlich whenceist das am häufigsten nach type. Die anderen sind spezielle Lösungen, die dieselbe Frage auf unterschiedliche Weise beantworten.

Was sollen wir stattdessen verwenden?

Wahrscheinlich whichzuerst wissen , ob es eine ausführbare Datei mit dem Namen des existiert command , dann typeund commanddann, wenn die command noch nicht gefunden worden: whence, where, whereis, whatis, hashin dieser Reihenfolge.


Shell-Skript zur Bereitstellung einer whichausführbaren Datei.

#! /bin/sh
set -ef; oldIFS=$IFS; IFS=:

say()( IFS=" "; printf "%s\n" "$*"; )
say "Simplified version of which."
usage(){ say Usage: "$0" [-f] args; }
if [ "$#" -eq 0 ]; then say Missing argument(s); usage; exit 2; fi

firstmatch=0
while getopts f whichopts; do
    case "$whichopts" in
        f) firstmatch=1 ;;
        ?) usage; exit 3 ;;
    esac
done
[ "$OPTIND" -gt 1 ] && shift `expr "$OPTIND" - 1`

allret=0; [ "$#" -eq 0 ] && allret=1
for program in "$@"; do
    ret=1
    for element in $PATH''; do
        case "$program" in
            */*) element="$program"; loop=0;;
            *)   element="${element:-.}/$program"; loop=1;;
        esac
        if [ -f "$element" ] && [ -x "$element" ]; then
            say "$element"
            ret=0
            if [ "$firstmatch" -eq 1 ] || [ "$loop" -eq 0 ]; then break; fi
        fi
    done
    [ "$ret" -eq 1 ] && allret=1
done

IFS="$oldIFS"
exit "$allret"

0

Wir hören oft das, was vermieden werden sollte. Warum? Was sollen wir stattdessen verwenden?

Ich habe das noch nie gehört. Bitte nennen Sie konkrete Beispiele. Ich würde mich um deine Linux-Distribution und die installierten Softwarepakete kümmern, da whichkommt das ja woher !

SLES 11.4 x 86-64

in tcsh version 6.18.01:

> which which

which: shell built-in command.

In der Bash-Version 3.2-147:

> which which

/usr/bin/which

> which -v

GNU which v2.19, Copyright (C) 1999 - 2008 Carlo Wood.
GNU which comes with ABSOLUTELY NO WARRANTY;
This program is free software; your freedom to use, change
and distribute this program is protected by the GPL.

whichist Teil von util-linux, einem Standardpaket, das von der Linux Kernel Organization zur Verwendung als Teil des Linux-Betriebssystems vertrieben wird. Diese anderen Dateien werden ebenfalls bereitgestellt

/bin/dmesg
/bin/findmnt
/bin/logger
/bin/lsblk
/bin/more
/bin/mount
/bin/umount
/sbin/adjtimex
/sbin/agetty
/sbin/blkid
/sbin/blockdev
/sbin/cfdisk
/sbin/chcpu
/sbin/ctrlaltdel
/sbin/elvtune
/sbin/fdisk
/sbin/findfs
/sbin/fsck
/sbin/fsck.cramfs
/sbin/fsck.minix
/sbin/fsfreeze
/sbin/fstrim
/sbin/hwclock
/sbin/losetup
/sbin/mkfs
/sbin/mkfs.bfs
/sbin/mkfs.cramfs
/sbin/mkfs.minix
/sbin/mkswap
/sbin/nologin
/sbin/pivot_root
/sbin/raw
/sbin/sfdisk
/sbin/swaplabel
/sbin/swapoff
/sbin/swapon
/sbin/switch_root
/sbin/wipefs
/usr/bin/cal
/usr/bin/chrp-addnote
/usr/bin/chrt
/usr/bin/col
/usr/bin/colcrt
/usr/bin/colrm
/usr/bin/column
/usr/bin/cytune
/usr/bin/ddate
/usr/bin/fallocate
/usr/bin/flock
/usr/bin/getopt
/usr/bin/hexdump
/usr/bin/i386
/usr/bin/ionice
/usr/bin/ipcmk
/usr/bin/ipcrm
/usr/bin/ipcs
/usr/bin/isosize
/usr/bin/line
/usr/bin/linux32
/usr/bin/linux64
/usr/bin/look
/usr/bin/lscpu
/usr/bin/mcookie
/usr/bin/mesg
/usr/bin/mkzimage_cmdline
/usr/bin/namei
/usr/bin/rename
/usr/bin/renice
/usr/bin/rev
/usr/bin/script
/usr/bin/scriptreplay
/usr/bin/setarch
/usr/bin/setsid
/usr/bin/setterm
/usr/bin/tailf
/usr/bin/taskset
/usr/bin/time
/usr/bin/ul
/usr/bin/uname26
/usr/bin/unshare
/usr/bin/uuidgen
/usr/bin/wall
/usr/bin/whereis
/usr/bin/which
/usr/bin/write
/usr/bin/x86_64
/usr/sbin/addpart
/usr/sbin/delpart
/usr/sbin/fdformat
/usr/sbin/flushb
/usr/sbin/freeramdisk
/usr/sbin/klogconsole
/usr/sbin/ldattach
/usr/sbin/partx
/usr/sbin/rcraw
/usr/sbin/readprofile
/usr/sbin/rtcwake
/usr/sbin/setctsid
/usr/sbin/tunelp

Mein util-linuxist Version 2.19. Versionshinweise sind leicht auf v2.13 vom (28-Aug-2007) zurückzuführen. Nicht sicher, was der Sinn oder das Ziel davon war, es wurde sicherlich nicht in dieser langwierigen Sache beantwortet, die 331 Mal aufgewertet wurde.


2
Beachten Sie, dass in der Frage nicht erwähnt wird, auf was sich Unix bezieht. Linux ist nur einer von wenigen.
Kusalananda

2
Wie Sie sehen which -v, ist dies GNU (das extravagante, das in der anderen Antwort erwähnt wurde und in keiner Weise spezifisch für Linux ist), nicht util-linux, für das AFAIK niemals ein whichHilfsprogramm enthielt . util-linux 2.19 ist aus dem Jahr 2011, GNU 2.19 ist aus dem Jahr 2008.
Stéphane Chazelas
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.