How to redirect stderr to /dev/null?
Asked Answered
zsh
D

4

5

I have a little function that allows me to activate my python virtual environment, which for different repos, is sometimes hidden (.venv) and sometimes not (venv). I'm having trouble suppressing stderr.

This is my little function (it lives in ~/.zshrc)

function venv {
    source `ls -a $PWD/*/bin/activate` > /dev/null
    if [[ $? -ne 0 ]] 
    then
        source `ls -a $PWD/.*/bin/activate` 
    fi
}

Only one of $PWD/*/bin/activate or $PWD/.*/bin/activate will exist, and I'd like to ignore the one that doesn't exist, with no error.

The problem is that this line

ls -a $PWD/*/bin/activate 2>&1 /dev/null

gives the terminal output

zsh: no matches found: /home/jokea/code/FlorA/*/bin/activate

My understanding is that 2>&1 /dev/null should suppress both stderr and stdout. So why isn't it working?

I've tried all the other variations I could find (>, >>, &>, >&, etc), but it still prints to terminal. How do I silence all output of my function?

Dasteel answered 20/12, 2020 at 12:56 Comment(1)
zsh: no matches found: -- that's not ls: no matches found; the error is being written by the shell before ls starts (as it's the shell responsible for replacing paths with wildcards with things that actually exist on the filesystem); so it's unsurprising that redirecting ls's stderr has no effect.Intrude
P
5

Here 2 things annoy me:

ls -a $PWD/*/bin/activate 2>&1 /dev/null

The order of redirection is not the right (you redirect the errors in the std output) and /dev/null is not prefixed with a stream redirection.

You should first redirect the std output into /dev/null and then redirect the std error into the same stream than the std output (that is /dev/null) :

ls -a $PWD/*/bin/activate > /dev/null 2>&1
Polyurethane answered 20/12, 2020 at 13:49 Comment(0)
M
1

It seems to me that the stderr redirection may not always be taken into account in zsh wildcard * expansions.

A cleaner solution that I commonly use is something like this:

find . -maxdepth 2 -name activate -type f -print |
  grep /bin/ |
  head -1 |
  while read script
  do source "$script"
  done
Maihem answered 3/3, 2021 at 1:14 Comment(0)
D
0

Using the other answers didn't quite get me there, but based on suggestions from davidxxx and rowboat (thanks for these), I hacked together this:

function venv {
    setopt +o nomatch
    ls -a $PWD/.*/bin/activate > /dev/null 2>&1
    if [[ $? -eq 0 ]] 
    then
        source `ls -a $PWD/.*/bin/activate` > /dev/null 2>&1 
    else
        ls -a $PWD/*/bin/activate > /dev/null 2>&1
        if [[ $? -eq 0 ]]  
        then   
           source `ls -a $PWD/*/bin/activate` > /dev/null 2>&1  
        else   
           echo "There's no Virtual Environment in $PWD"     
        fi
    fi
}

It's admittedly a bit garbagey, but it works. I can now set virtual environments without any errors.

Dasteel answered 21/12, 2020 at 19:27 Comment(1)
Why is checking $? to see if a command succeeded or not an antipattern? -- if you must use ls, then consider if ls -a ...; then ...; else ..., but it's better to follow the practices given in BashFAQ #4 (which in this particular case mostly apply to zsh as well).Intrude
D
0

2>&1 is one redirection - send fd2 into fd1, effectively sending stderr into stdout. The second bit, /dev/null doesn't do any redirection. You probable meant to redirect stdout to /dev/null, so it should be > /dev/null, but as davidxxx mentioned, it should be the other way around (not especially intuitive, imo).

If you just want to redirect one, then just do that:

$ (echo stdout; ls /dev/error) 1>/dev/null
ls: /dev/error: No such file or directory
$ (echo stdout; ls /dev/error) 2>/dev/null
stdout
$

and, of course, you can do both individually (order doesn't matter since it's /dev/null - I guess it would for a regular file?):

$ (echo stdout; ls /dev/error) 2>/dev/null 1>/dev/null
$

You can send stderr(2) into fd1 so it is 'merged' with stuff written to stdout - it looks the same as if you did nothing:

$ (echo stdout; ls /dev/error) 2>&1
stdout
ls: /dev/error: No such file or directory

but if you put it in a subshell and redirect the resulting stderr(2) to /dev/null, you can see that it is actually part of stdout(1) because nothing changes:

$ ((echo stdout; ls /dev/error) 2>&1) 2>/dev/null
stdout
ls: /dev/error: No such file or directory

as opposed to:

$ ((echo stdout; ls /dev/error) ) 2>/dev/null
stdout

So, yeah, you wanted:

$ (echo stdout; ls /dev/error) >/dev/null 2>&1

which is the same as:

$ (echo stdout; ls /dev/error) 1>/dev/null 2>&1

and, indeed has the same effect as (open /dev/null as fd[2], fd[1] = fd[1]):

$ (echo stdout; ls /dev/error) 2>/dev/null 1>&2

Maybe a good tip is to think of the low level system calls - I think it might help make the order make sense.

2>/dev/null - open /dev/null with file descriptor 2.

1>&2 - instead of writing to fd 1, write to fd 2 (which has been opened on /dev/null). You could imagine it internally doing fd[2] = fd[1];.

Of course, if you don't care about the output, then perhaps you should consider closing them both:

$ (echo stdout; ls /dev/error) 1>&- 2>&-
$

This could cause problems if you don't close stderr(2) since zsh/echo will try to write to stdout(1), which is closed:

$ (echo stdout; ls /dev/error) 1>&-
zsh: write error: bad file descriptor
ls: /dev/error: No such file or directory

but it's an option to consider. Works nicely for stderr, it seems (nowhere to complain to).

Disfeature answered 17/8, 2022 at 15:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.