2

I'm learning bash redirection. I expect the following code to do the equivalent of ls | grep foo | grep bar.

#!/bin/bash                                                                     

{
    {
        ls | grep foo
    } 2>&1 1>&4 | grep bar
} 4>&1

This matches foo but not foo and bar. I thought this will work since 2>&1 copies pipe in fd 1 to fd 2, and then we reset fd 1 to stdout.

Can you fix this code while keeping the braces(contexts)? If I remove 2>&1 1>&4 it works as expected. But I would like to learn.

I think on the left side of pipe | it only expects stdout so even if I copy the pipe to fd 2, it will still only look for fd 1 on the left side of pipe.

Refer: https://wiki.bash-hackers.org/howto/redirection_tutorial#an_example

In the referenced link above, there is a diagram where fd 2 of cmd1 points to fd 0 of cmd3. I'm trying to verify this using my example.

4
  • I don't see how you arrived at your expectation - there is no duplication of ls output (unlike say, zsh, where multiple redirections could be used to send the same output to multiple commands), so since you have only one invocation of ls in your script, only one of ls | grep \~; ls | grep sh$ would be correct. – muru Feb 1 at 6:24
  • @muru yes i think it is doing ls | grep \~ | grep sh$ – Logan Lee Feb 1 at 6:56
  • welcome to unix.se! you : first you create a fd4 duplicating fd1, ie : 4 now goes to where 1 is currently going (stdout by default). Then you : 2>&1 : 2 goes to where 1 is now pointing (ie, stdout). then 1>&4: 1 now goes to fd 4 (the one of the surrounding shell). you grep bar : on stdin (fd1), so on stderr only of the { ls | grep foo } .... I think you should put the | grep bar after the 4>&1 – Olivier Dulac Feb 1 at 6:57
  • You may want to read what the original usenet discussion was about and that wiki page from the beginning. – Stéphane Chazelas Feb 1 at 10:35
0

your attempt:

{
    {
        ls | grep foo # goes to stdout (fd 1)
    } 2>&1 1>&4 | grep bar  # at this level: 2 now points to where fd1 is pointing (stdout), and 1 is *then* directed to the new fd 4 (ie to stdin of the outside curly)
} 4>&1  ## at this shell 'level', a new fd 4 is now pointing to where 1 is pointing: stdout

: the grep bar is therefor done only on stderr of the { ls | grep foo }, and thus on an empty stdin, as only stderr of the { ls | grep foo } goes to stdout of the { ls | grep foo }. This will never see filenames. ths filenames goes to fd 4 try:

{
    {
        ls | grep foo
    } 2>&1 1>&4
} 4>&1 | grep bar

or

{
    {
        ls | grep foo
    } 2>&1 1>&4 | <&4 grep bar
} 4>&1

and compare...

note: if you meant for stderr of the inner commands to also go to stdout, it needs to be duplicated in 2nd position: 1>&4 2>&1 [ie : 1 now goes to fd 4, and then 2 goes to where 1 is now pointing, ie fd 4 as well]

4
  • As far as i know 2>&1 copies pipe in fd 1 to fd 2 because it is left side of pipe |. – Logan Lee Feb 1 at 7:29
  • <&4 grep bar, would redirect grep stdin to the same resource as open on the outer stdout, so in the OP's example /dev/pts/5 so read what you type. In the general case (not in a terminal), it would cause grep to fail as that open file description would be open in write mode and grep would try to read from it. – Stéphane Chazelas Feb 1 at 7:34
  • @StéphaneChazelas: thx, I said to try those 2 and compare: because i expect the one with <&4 grep bar : to not work (as there are no fd 4 defined at this level, and the pipe has nothing to carry over to the right side). i have a bit trouble understanding the rest of your remark though ... why would this be always pts/5 ? and why is a "<" opening in write mode? – Olivier Dulac Feb 1 at 22:08
  • @LoganLee : not because it is left of the pipe. any "x>&y" : is a way to tell "now x points to where y is currently pointing at". ie, it copies the destination of y into x. The order of those redirections matter: ls -ld / /inexistant 2>&1 >/dev/null redirects fd2 to stdout (as fd1 goes to stdout at first) and then redirects 1 to /dev/null : So you only see on stdout: the error message "ls: cannot access '/inexestant': No such file or directory" (but not the info of "/", which goes, via fd1, to /dev/null). If you put >/dev/null 2>&1: boh fd 1 and 2 are discarded by going to /dev/null – Olivier Dulac Feb 1 at 22:17
0

As far as I can gather the following is true.

This

{
    {
        ls | grep foo
    } 2>&1 1>&4 | grep bar
} 4>&1

is equivalent to

(ls | grep foo 2>&1 1>&4 | grep bar) 4>&1

And it fails because fd 4 is, for instance, /dev/pts/0 and 1>&4 will result in fd 1 having the same /dev/pts/0 instead of pipe.

1
  • Note that ... { ls | grep foo; } 2>&1 ... sends the standard error stream of both ls and grep to grep bar, while ... ls | grep foo 2>&1 ... only sends to grep bar the standard error of grep foo (ls' standard error is not redirected). – fra-san Feb 1 at 18:46
-1

It looks like you are expecting the re-direction to make a copy of the data? Consider using tee. As you are learning, I won't spoil things by saying more.

Also use shellcheck to check your code. It will help you find most errors more quickly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.