Search code examples
bashterminaloutput-redirect

Conditionally directing a command's output to /dev/null in bash


I have following bash script:

flag=false
command_name \
$(  flag == false  && printf %s '>/dev/null') 

I expect no output at Terminal but I still get some. If I redirect output to /dev/null on the same line as command-name without that expansion then it gets suppressed.

Command is dx tool from android SDK

Edit 1: Here the code from script

dx \
    --dex \
    $( ( (( flag_v == 1 )) || (( flag_v == 'd' ))) && printf %s '--verbose') \
    --no-strict \
    --output="../"$app_name.jar \
    $(find . -type f -name '*.class') \
    $( $dexflag == false && printf %s '>/dev/null')

As I run the tool and it works as expected. I do not think that it could be an error stream.


Solution

  • Conditionally Redirecting Stdout

    Redirections are shell syntax -- they have to be recognized at a parsing phase that comes before parameter expansion, so you can't generate them via variable expansion (without committing evil).

    What you can do (in bash 4.1 or later) is have an unconditional redirection, but have the thing it redirects to change:

    # Create an out_fd variable that points to stdout (FD 1) if dexflag != "false", or to a new
    # handle on /dev/null otherwise
    if [[ $dexflag = false ]]; then
      exec {out_fd}>/dev/null # maybe put 2>&1 as well to suppress stderr
    else
      out_fd=1 # use FD 1 (stdout)
    fi
    
    # run dex with its stdout redirected to the FD number in "out_fd"
    dex ... >&"$out_fd"
    
    # if out_fd is not stdin/stdout/stderr, then go ahead and close it when done.
    (( out_fd > 2 )) && exec {out_fd}>&-
    

    Note:

    • A string comparison is done in the form [[ $var = $pattern ]] (or [[ $var = "$string" ]] to do an exact match). See the bash-hackers' wiki on the conditional expression.
    • In bash 4.1 or later, exec {fd_varname}>file opens file, and puts the file descriptor number pointing to that file in the variable fd_varname. exec {fd_varname}>&- closes the file descriptor whose number is stored in fd_varname.
    • With older versions of bash, you can still do this logic, but instead of having a file descriptor number automatically assigned, you'll need to do so by hand, manually assigning an otherwise-unused FD number that isn't any of 0, 1 or 2 (which are reserved for stdin, stdout and stderr). Thus, in that case, it might be exec 3>/dev/null or exec 3>&1 in the if branches, >&3 on the dex command, and exec 3>&- to close it.

    Safely Generating Argument Lists Conditionally

    See BashFAQ #50 for a long discussion. In short, though: For everything but the redirection to /dev/null, there's one simple change needed to bring this in line with best practices: Use an array.

    #!/bin/bash
    
    args=( )
    
    case $flag_v in
      1|d) args+=( --verbose ) ;;
    esac
    
    while IFS= read -r -d '' filename; do
      args+=( "$filename" )
    done < <(find . -type f -name '*.class' -print0)
    
    dx --dex --no-strict --output="../$app_name.jar" "${args[@]}"
    
    • See BashPitfalls #1 describing why $(find ...) (like $(ls ...)) is unsafe, and Using Find going into best practices.
    • See BashFAQ #24 to understand why while read ...; do ...; done < <(find ...) is used instead of find ... | while read ...; do ...; done.