Warum können bestimmte Programme (wie Readlink) keine Eingaben von einer Pipe entgegennehmen?

7

Ich habe einen symbolischen Link zu einem Skript in meiner $PATHDatei, dessen Datei ich bearbeiten wollte. Ich habe den Dateipfad vergessen und daher Folgendes versucht:

$ which my_script_link | readlink

Ich habe erwartet, dass der Dateipfad ausgegeben wird, aber stattdessen wird er ausgegeben

> readlink: missing operand
> Try 'readlink --help' for more information

Ich habe in anderen Situationen bereits ein ähnliches Verhalten festgestellt (z. B. beim Versuch, eine Liste von Dateien zur Bearbeitung in vim zu leiten). Ich weiß, dass es Problemumgehungen gibt, wie bei einer Unterschale readlink $(which my_script_link), aber ich möchte verstehen, warum Rohrleitungen in dieser Situation nicht so funktionieren, wie ich es mir vorstelle .

Vielen Dank!

Nathan Wallace
quelle

Antworten:

11

Einfach ausgedrückt, weil das Programm nicht dafür geschrieben ist. Es liegt an den Programmierern zu entscheiden, ob ihre Software von STDIN lesen kann oder ob eine Eingabedatei erforderlich ist. Im Fall von readlinkheißt es auf seiner manSeite (Hervorhebung von mir):

readlink - Druckt aufgelöste symbolische Links oder kanonische Dateinamen

SYNOPSIS
readlink [OPTION] ... DATEI ...

In der Regel sind Programme, die Eingaben von STDIN übernehmen, so konzipiert, dass sie diese Eingaben irgendwie analysieren. Wenn Sie Daten an readlinkdiese weiterleiten, wird ein Textstrom empfangen, und Sie wissen nicht, was Sie damit tun sollen, da nur Dateien und nicht deren Inhalt behandelt werden. Gleiches gilt für Programme wie lsoder cdoder cpusw. Nur Programme, die sich mit Text- / Datenströmen befassen, können Eingaben von einer Pipe akzeptieren.

terdon
quelle
7

Ob ein Programm seine Eingabe von stdinoder als Befehlszeilenargumente erhält, ist Sache des Designers. Beide Ansätze haben ihre Vorzüge. Bei Programmen, die ausschließlich mit Dateien arbeiten, ist es jedoch normalerweise bequemer, die Dateinamen als Befehlszeilenargumente zu übergeben, als durch stdin.

Der offensichtlichste Grund ist, dass der übliche Fall, nur ein Programm in einer Datei auszuführen, einfacher einzugeben ist: readlink filestatt echo file | readlink.

Ein subtileres Problem ist die Korrektheit. Das Problem ist, dass stdindas Programm beim Durchlaufen von Dateinamen in der Lage sein muss, einen Dateinamen von einem anderen zu unterscheiden. Dies geschieht häufig unter der Annahme, dass die Dateinamen durch Leerzeichen oder Zeilenumbrüche getrennt sind. Dies ist jedoch falsch, da Dateinamen Leerzeichen enthalten können. Eine bessere Möglichkeit besteht darin, die Dateinamen durch Null-Bytes zu trennen. Es kann jedoch unpraktisch sein, eine Liste von durch Nullen getrennten Dateien zu erstellen.

Durch das Übergeben von Dateinamen in der Befehlszeile wird dieses Problem vermieden, da die Shell das gesamte Parsen und Zitieren für das Programm übernimmt. Sie können dies eingeben touch $'foo\nbar'und touchsehen dies korrekt als einen Dateinamen, der eine neue Zeile enthält, ohne dass Sie selbst spezielle Analysen durchführen oder Anführungszeichen setzen müssen.

Wenn Sie jedoch Dateien stdinfür ein bestimmtes Programm weitergeben möchten, können Sie dies tun. Dafür ist da xargs. xargsMit dieser Option können Sie ein Programm verwenden, das nur Argumente in der Befehlszeile akzeptiert, und stattdessen die Argumente durchgehen lassen stdin.

Mit anderen Worten, damit können Sie Folgendes tun : which my_script | xargs readlink.

Wenn Sie immer so readlinkarbeiten möchten, können Sie einen Alias ​​erstellen : alias readlink="xargs readlink". Auf diese Weise können Sie which my_script | readlinkwie ursprünglich gewünscht tippen .

Matt
quelle
Dank dafür. Ich habe es nie verstanden xargs(habe mich nie darum gekümmert, Google darüber zu informieren oder die Manpage zu lesen), aber das macht Sinn. Das einzige Mal, dass ich xargs verwendet habe, ist das Öffnen einer Liste von Dateien in vim, a la find *.rb | xargs vim. Jetzt verstehe ich, was das tut und warum das funktioniert, wenn find *.rb | vimes nicht funktioniert. Vielen Dank!
Nathan Wallace
4

Für Befehle, die keine Argumente über STDIN annehmen, können Sie stattdessen dieses Code-Pragma verwenden:

$ readlink $(which my_script_link)

Beispiel

$ ln -s /bin/ls ~/bin/somecmd

Überprüfen Sie nun, ob es auf der $PATH.

$ which somecmd
~/bin/somecmd

Oder der bevorzugte Weg, indem Sie typeFolgendes verwenden which:

$ type somecmd
somecmd is /home/saml/bin/somecmd

Oder nur der Wert:

$ type -P somecmd 
/home/saml/bin/somecmd

Jetzt rennen wir readlink:

$ readlink $(type -P somecmd)
/bin/ls
slm
quelle
Das habe ich auch in der Frage gesagt. Ich bin allerdings neugierig, warum wäre typedas besser which?
Nathan Wallace
1
@ NathanWallace - Ich habe bestätigt, dass dies der richtige Weg ist. Hervorgehoben wurde auch die Verwendung von Typ, über die Terdon in meiner Antwort bereits die Argumentation behandelte. Siehe diese U & L Q / A: unix.stackexchange.com/questions/85249/…
slm