Почему некоторые программы (например, readlink) не могут принимать входные данные из канала?

Почему некоторые программы (например, readlink) не могут принимать входные данные из канала?

У меня есть символическая ссылка на скрипт в моем $PATHфайле, который я хотел отредактировать. Я забыл путь к файлу, поэтому я попытался сделать следующее:

$ which my_script_link | readlink

Я ожидал, что это выведет путь к файлу, но вместо этого он вывел

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

Я уже видел подобное поведение в других ситуациях (например, при попытке передать список файлов в vim для редактирования). Я знаю, что есть обходные пути, например, subshell readlink $(which my_script_link), но я хочу понятьпочемуВ этой ситуации трубопровод не работает так, как я думаю.

Спасибо!

решение1

Проще говоря, потому что программа не написана для этого. Программисты должны решить, может ли их программное обеспечение читать из STDIN или ему требуется входной файл. В случае readlink, на его manстранице указано (выделено мной):

readlink - печать разрешенасимволические ссылкиили каноническийимена файлов

СИНТАКСИС
readlink [ВАРИАНТ]...ФАЙЛ...

Как правило, программы, которые принимают входные данные из STDIN, предназначены для того, чтобы каким-то образом анализировать эти входные данные. Когда вы передаете данные в конвейер, readlinkон получает текстовый поток и не знает, что с ним делать, поскольку он имеет дело только с файлами, а не с их содержимым. То же самое относится к таким программам, как lsили cdили cpи т. д. Только программы, которые имеют дело с потоками текста/данных, могут принимать входные данные из конвейера.

решение2

Получает ли программа свои входные данные из stdinили в качестве аргументов командной строки, зависит от разработчика. Оба подхода имеют свои достоинства. Однако для программ, работающих строго с файлами, обычно удобнее передавать имена файлов в качестве аргументов командной строки, а не через stdin.

Самая очевидная причина заключается в том, что в общем случае, когда требуется просто запустить программу для файла, проще ввести: readlink fileвместо echo file | readlink.

Более тонкая проблема — корректность. Проблема в том, что когда имена файлов передаются через stdin, программа должна уметь отличать одно имя файла от другого. Часто это делается, предполагая, что имена файлов разделены пробелами или символами новой строки, но это неверно, поскольку имена файлов могут включать пробелы. Лучший способ — разделить имена файлов нулевыми байтами, но может быть неудобно генерировать список файлов, разделенных нулями.

Передача имен файлов в командной строке позволяет избежать этой проблемы, поскольку оболочка обрабатывает весь разбор и цитирование для программы. Вы можете ввести touch $'foo\nbar'и touchправильно увидеть это как одно имя файла, содержащее новую строку, без необходимости обрабатывать какой-либо специальный разбор или цитирование.

При всем при этом, если вы хотите передать файлы stdinдля определенной программы, вы можете. Это то, что xargsнужно для. xargsпозволяет вам взять программу, которая принимает только аргументы в командной строке, и вместо этого заставить ее принимать свои аргументы через stdin.

Другими словами, он позволяет вам сделать следующее: which my_script | xargs readlink.

Если вы хотите, чтобы readlinkработа всегда выполнялась таким образом, вы можете создать псевдоним: alias readlink="xargs readlink". Это позволит вам набирать which my_script | readlink, как вы изначально и хотели.

решение3

Для команд, которые не принимают аргументы через STDIN, вы можете использовать следующую прагму code:

$ readlink $(which my_script_link)

Пример

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

Теперь проверьте, есть ли он на $PATH.

$ which somecmd
~/bin/somecmd

Или предпочтительный способ, используя typeвместо which:

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

Или просто значение:

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

Теперь бежим readlink:

$ readlink $(type -P somecmd)
/bin/ls

Связанный контент