10

I have a symbolic link to a script in my $PATH whose file I wanted to edit. I forgot the filepath, so I tried to do the following:

$ which my_script_link | readlink

I expected that to output the filepath, but instead it output

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

I have seen similar behavior before in other situations (like trying to pipe a list of files into vim for editing). I know there are workarounds, like a subshell readlink $(which my_script_link), but I want to understand why piping doesn't work the way I think it should in this situation.

Thanks!

3 Answers3

14

Simply put, because the program is not written to do so. It is up to the programmers to decide if their software can read from STDIN or if it requires an input file. In the case of readlink, its man page states (emphasis mine):

readlink - print resolved symbolic links or canonical file names

SYNOPSIS
readlink [OPTION]... FILE...

As a general rule, programs that take input from STDIN are designed to somehow parse that input. When you pipe data to readlink it receives a text stream and has no idea what to do with it since it only deals with files and not their contents. The same is true for programs like ls or cd or cp etc. Only programs that deal with text/data streams can accept input from a pipe.

terdon
  • 242,166
10

Whether a program takes its input from stdin or as command line arguments is up to the designer. Both approaches have their merits. For programs that operate strictly on files, however, it's usually more convenient to pass the filenames as command line arguments rather than through stdin.

The most obvious reason is that the common case, that of just running a program on a file, is easier to type: readlink file instead of echo file | readlink.

A subtler issue is correctness. The problem is that when filenames are passed through stdin, the program needs to be able to distinguish one filename from another. Often, this is done by assuming the filenames are separated by whitespace or newlines, but this is incorrect since filenames can include whitespace. A better way is to separate the filenames with null bytes, but it can be inconvenient to generate a list of files separated by nulls.

Passing filenames on the command line avoids this problem because the shell handles all the parsing and quoting for the program. You can type touch $'foo\nbar' and touch correctly sees this as one filename containing a newline, without having to handle any special parsing or quoting itself.

All that being said, if you'd like to pass files through stdin for a particular program, you can. This is what xargs is for. xargs lets you take a program that only accepts arguments on the command line, and instead make it take its arguments through stdin.

In other words, it lets you do this: which my_script | xargs readlink.

If you wanted to always make readlink work this way, you could make an alias: alias readlink="xargs readlink". That would allow you to type which my_script | readlink, as you originally wanted.

Matt
  • 1,586
  • Thanks for that. I never understood xargs (never bothered to Google about it or read the man page), but that makes sense. The only time I've ever used xargs is to open a list of files in vim, a la find *.rb | xargs vim. Now I understand what that's doing and why that works when find *.rb | vim doesn't. Thanks! – Nathan Wallace Oct 03 '13 at 20:10
4

For commands that don't take arguments via STDIN you can use this code pragma instead:

$ readlink $(which my_script_link)

Example

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

Now check that it's on the $PATH.

$ which somecmd
~/bin/somecmd

Or the preferred way, using type instad of which:

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

Or just the value:

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

Now we run readlink:

$ readlink $(type -P somecmd)
/bin/ls
Joseph R.
  • 39,549
slm
  • 369,824
  • I said as much in the question. I am curious, though, why would type be preferable to which? – Nathan Wallace Oct 03 '13 at 20:18
  • 1
    @NathanWallace - I was confirming that this is the way to do it. Was also highlighting the use of type over which in my answer, terdon already covered the reasoning. See this U&L Q/A: http://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then/85250#85250 – slm Oct 03 '13 at 20:21