1

Is there an easy way to what the following commands will do?

watch foo 2>/dev/null | tr ' ' '-'
strace foo 2>/dev/null | tr ' ' '-'
something_else foo 2>/dev/null | tr ' ' '-'    

Is there a common expectation how something_else should behave? Are there shell constructs that can disambiguate these commands (like which program's output is redirected)?

sevo
  • 1,237
  • 1
    Do you have something specific in mind about those commands, or is there some reason you picked those two and not e.g. sh -c foo 2>/dev/null | tr ' ' '-'? In general, a command might do what ever it needs to do to complete its task, so it's hard to expect anything about something_else without knowing what it does. The same goes for watch and strace, but they have man pages and other documentation that tells us what they do. – ilkkachu Aug 15 '18 at 21:19
  • I have edited my answer a little bit. Let me know if anything is unclear. – Sergiy Kolodyazhnyy Aug 16 '18 at 02:19
  • 1
    In UNIX, a program's open files are stored in a table as numbers called file descriptors (fd's). If a program wants to, eg write to a file that it has opened, it gives the kernel some bytes and the fd and the kernel will do the work. By convention programs should always emit error messages to a specific already open file with file descriptor 2. When the shell runs something_else in the way ur sample code does, it arranges for something_else's fd, 2, to be open to a pseudo file that discards whatever's written to it. BTW, this convention causes fd 2 to be named standard error. – ctt Aug 17 '18 at 11:27
  • @sevo ^ this is only common expectation you have have of line 3 in your sample code, and even then, it depends on something_else conforming to the standard error convention. In reality, it could emit errors anywhere, but it should only emit them to file descriptor 2, the standard error stream. – ctt Aug 17 '18 at 11:28

2 Answers2

2

The commands themselves do not handle the redirections. When the shell creates a new process in which to run the command, the shell will set up the IO as directed. The command itself just runs without having to care about it.

Your something_else command will just emit output to stdout and stderr as usual.

glenn jackman
  • 85,964
1

TL;DR:

Is there a common expectation how something_else should behave?

Yes, as a regular command. The shell doesn't understand command1 command2 as two commands, it's one command and one argument (even if it may be a name of a valid binary found in /bin for example). It's the command1 that later runs command2 via exec() syscall, not the shell.

Are there shell constructs that can disambiguate these commands (like which program's output is redirected)

No, someting_else would not be treated in any special way. As said above, there's no construct other than one command and one argument. It also seems the confusion in this question is that both strace and foo are assumed to be run by the shell, but in fact it's a chain of parent-child processes.

How shell processes commands and redirections

Bourne-like shells ( that's bash, dash, ksh ) all follow POSIX rules on how commands are interpreted. POSIX specifies:'A "simple command" is a sequence of optional variable assignments and redirections, in any sequence, optionally followed by words and redirections, terminated by a control operator.' This can be put into a form:

[VAR=foo BAR=baz] command1 [arg1, arg2...] [ n>m ]

The general rule is that redirection applies to a command, which would be left most non-assignment word. In your example, strace is the command and foo is the argument to strace. The redirections apply only to the command, not the arguments, that is to strace. The foo isn't run by the shell, but by strace, in which case foo becomes a child process of strace. Similarly, somethingelse would be treated as a command with foo as its argument.

File descriptors

As far as non built-in commands go, they're a child process of the shell, and child processess inherit shell's file descriptors, so they generally don't manage redirections - they receive "prepackaged" destinations, so as far as strace and 2>/dev/null go, it probably wouldn't care what file descriptor 2 actually is (unless it's actively checking on the source code level). strace will still write output to file descriptor 2, but shell already wired-up that file descriptor to point to /dev/null.

Because file descriptors are inherited, this also explains why if you do something like strace stat noexist 2>stracelog.txt both error streams from both strace and stat go into the same file. By contrast, some commands allow explicitly specifying destination as one of their options. Thus, with strace -o tracelog.txt stat noexist 2>stracelog.txt you only have output from stat in the stracelog.txt file - even though file descriptor is inherited, now -o flag is the property of strace, and output is managed by the command even if the file descriptor is inherited.

This also gives us a small hint: in theory commands could "redirect" in the sense of duplicating file descriptors onto existing ones via dup2() syscall, which is exactly the same mechanism shells would use, but as far as > type of redirection symbols - that's still under shell control and hence only interpreted by the parent shell.

Why 2>/dev/null ?

It's generally accepted that diagnostic output goes to stderr. In fact we already have two excellent posts on this topic:

watch and strace in your examples follow the convention, that's all. There's no requirement per se to specify 2>/dev/null to hide output of such commands.

If you do want to specify redirections for the command that you pass as an argument, to strace for example, you'd need to have a shell around it. For instance,

strace -f bash -c 'stat /etc/passwd nonexisting 2>/dev/null'

Notice the use of -f flag, since if your focus is on tracing syscalls as applied to stat command, you might not see them without -f flag.