How to ignore xargs commands if stdin input is empty?

后端 未结 6 2035
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-28 04:29

Consider this command:

ls /mydir/*.txt | xargs chown root

The intention is to change owners of all text files in mydir to root

相关标签:
6条回答
  • 2020-11-28 05:10

    For GNU xargs, you can use the -r or --no-run-if-empty option:

    --no-run-if-empty
    -r
    If the standard input does not contain any nonblanks, do not run the command. Normally, the command is run once even if there is no input. This option is a GNU extension.

    0 讨论(0)
  • 2020-11-28 05:10

    In terms of xargs, you can use -r as suggested, however it's not supported by BSD xargs.

    So as workaround you may pass some extra temporary file, for example:

    find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root $(mktemp)
    

    or redirect its stderr into null (2> /dev/null), e.g.

    find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root 2> /dev/null || true
    

    Another better approach is to iterate over found files using while loop:

    find /mydir -type f -name "*.txt" -print0 | while IFS= read -r -d '' file; do
      chown -v root "$file"
    done
    

    See also: Ignore empty results for xargs in Mac OS X


    Also please note that your method of changing the permissions isn't great and it's discouraged. Definitely you shouldn't parse output of ls command (see: Why you shouldn't parse the output of ls). Especially when you're running your command by root, because your files can consist special characters which may be interpreted by the shell or imagine the file having a space character around /, then the results can be terrible.

    Therefore you should change your approach and use find command instead, e.g.

    find /mydir -type f -name "*.txt" -execdir chown root {} ';'
    
    0 讨论(0)
  • 2020-11-28 05:16

    Users of non-GNU xargs may take advantage of -L <#lines>, -n <#args>, -i, and -I <string>:

    ls /empty_dir/ | xargs -n10 chown root # chown executed every 10 args or fewer
    ls /empty_dir/ | xargs -L10 chown root # chown executed every 10 lines or fewer
    ls /empty_dir/ | xargs -i cp {} {}.bak # every {} is replaced with the args from one input line
    ls /empty_dir/ | xargs -I ARG cp ARG ARG.bak # like -i, with a user-specified placeholder
    

    Keep in mind that xargs splits the line at whitespace but quoting and escaping are available; RTFM for details.

    Also, as Doron Behar mentions, this workaround isn't portable so checks may be needed:

    $ uname -is
    SunOS sun4v
    $ xargs -n1 echo blah < /dev/null
    
    $ uname -is
    Linux x86_64
    $ xargs --version | head -1
    xargs (GNU findutils) 4.7.0-git
    $ xargs -n1 echo blah < /dev/null
    blah
    
    0 讨论(0)
  • 2020-11-28 05:16

    man xargs says --no-run-if-empty.

    0 讨论(0)
  • 2020-11-28 05:16

    This is a behaviour of GNU xargs which can be supressed by using -r, --no-run-if-empty.

    The *BSD variant of xargs has this behavoir on default, so -r is not needed. Since FreeBSD 7.1 (released in january 2009) an -r argument is accepted (witch does nothing) for compatiblity reasons.

    I personally prefer using longopts in scripts but since the *BSD xargs does not uses longopts just use "-r" and xargs will act the same on *BSD an on linux systems

    xargs on MacOS (currently MacOS Mojave) sadly don't supports the "-r" argument.

    0 讨论(0)
  • 2020-11-28 05:19

    On OSX: Bash reimplementation of xargs dealing with the -r argument, put that e.g. in $HOME/bin and add it to the PATH:

    #!/bin/bash
    stdin=$(cat <&0)
    if [[ $1 == "-r" ]] || [[ $1 == "--no-run-if-empty" ]]
    then
        # shift the arguments to get rid of the "-r" that is not valid on OSX
        shift
        # wc -l return some whitespaces, let's get rid of them with tr
        linecount=$(echo $stdin | grep -v "^$" | wc -l | tr -d '[:space:]') 
        if [ "x$linecount" = "x0" ]
        then
          exit 0
        fi
    fi
    
    # grep returns an error code for no matching lines, so only activate error checks from here
    set -e
    set -o pipefail
    echo $stdin | /usr/bin/xargs $@
    
    0 讨论(0)
提交回复
热议问题