15

The code for my website uses this piece of code for automatic deployment on the server (Ubuntu).

cmd = 'cd ' + checkout_dir + ' && ' + svn_command + " st | awk '{print $2}' | grep -v ^deploy | tac | xargs -r" + svn_command + " revert -R && " + svn_command + ' up -r ' + options.revision

What this command does is it cd into checkout directory, runs svn status, and then extracts the filename ($2), removes the deploy directory and all its files from the list (I don't want to revert it). If there are no argument it does not run the svn revert command, else it does.

Unfortunately the xargs -r does not work on my machine (Mac OS X 10.8). So I am stuck here, can anyone help?

kenorb
  • 155,785
  • 88
  • 678
  • 743
Anubhav Agarwal
  • 1,982
  • 5
  • 28
  • 40

4 Answers4

5

Indeed, the BSD implementation of xargs doesn't have the -r flag (--no-run-if-empty). The GNU version in Linux has it.

Here's one way to work around the issue in a way that works in both Linux and BSD:

... | (grep -v ^deploy || echo :) | xargs svn revert

The grep ... || echo : in the middle will generate a line with a : in it in case the output of grep is empty. It's a bit dirty, because xargs will still run the command svn revert :. If your repository doesn't contain the file : then this should have no effect, so it can be acceptable. The : could be anything else, as long as there is no such file in your repository.

Finally, as @tripleee pointed out, the grep ... || echo : must be enclosed within (...), because:

the || has higher precedence than |, and thus terminates the (first) pipeline.

Your code looks like a Python string. It will be more readable this way:

kwargs = {
  'svn': svn_command,
  'dir': checkout_dir,
  'revno': options.revision,
}
cmd = "cd {dir} && {svn} st | awk -v r=1 '$2 ! ~ /deploy/ {{ print $2; r=0 }} END {{ r || print \":\" }}' | xargs {svn} revert && {svn} up -r {revno}".format(**kwargs)

I made some changes to your original:

  • Moved the logic of the grep inside awk, as @tripleee suggested. Notice that since the grep hack is not needed anymore, there is also no more need to wrap within (...)
  • Dropped the tac, as I don't see the point in it
  • Dropped the -R from svn revert, because I don't think you need it
Community
  • 1
  • 1
janos
  • 120,954
  • 29
  • 226
  • 236
  • How does this help at all? The problem is not empty lines, it's *no* lines. – tripleee Jun 07 '14 at 16:28
  • @tripleee He wants to use the `-r` flag of `xargs`, but cannot, because it doesn't exist in BSD. He wants to use the flag because he doesn't want to run `svn revert -R` without arguments. `svn revert -R something` will revert the changes in "something" only, and `svn revert -R` without arguments would revert everything. He doesn't want to revert everything. – janos Jun 08 '14 at 08:39
  • I understand the question. `xargs -r` is useful to suppress the action when there is no input; `grep .` does nothing to generate input when there is none (and how could it?), so the suggested workaround in this answer doesn't help at all. – tripleee Jun 08 '14 at 08:55
  • @tripleee aaaaaaah, ok I see! You're absolutely right, I corrected my answer. – janos Jun 08 '14 at 12:18
  • Still not quite there; you will need parentheses around the pipeline like `( cmds || echo : ) | xargs` because the `||` has higher precedence tjan `|` and thus terminates the (first) pipeline. – tripleee Jun 08 '14 at 12:20
  • Or just factor the `echo :` into the script: `awk -v r=1 '$2 ! ~ /deploy/ { print $2; r=0 } END { r || print ":" }'` – tripleee Jun 08 '14 at 12:41
  • @tripleee thanks for the tips. I improved the `awk`. Are you sure about the `( ... )` thing though? For example `ls | grep . || echo : | xargs echo` and `ls | grep nonono || echo : | xargs echo` work as expected for me: in the first one all matches reach the `xargs`, in the second the `:` reach the `xargs`. Am I missing something? – janos Jun 08 '14 at 13:52
  • It's not reaching `xargs` in the first case; use e.g. `xargs wc` instead to see this more clearly. – tripleee Jun 08 '14 at 15:28
  • The macOS version of xargs seems to do `-r` by default: if there is no input, then the command is not run. – mjs Aug 02 '19 at 20:28
  • The utility `xargs` on mac supports `-r` now, while `--no-run-if-empty` is still not supported. OS Version: `Darwin 21.1.0 Darwin Kernel Version 21.1.0: Wed Oct 13 17:33:23 PDT 2021; root:xnu-8019.41.5~1/RELEASE_X86_64 x86_64` – martian Nov 18 '21 at 12:29
3

Not pretty, but hopefully a workaround.

cmd = 'cd ' + checkout_dir + ' && ' + 
    'r=$(' svn_command + ' st | ' +
        "awk '$2 !~ /^deploy/{print $2}' | tac) && " +
    'test "$r" && ' +
    svn_command + ' revert -R $r && ' +
    svn_command + ' up -r ' + options.revision

I'm not convinced that the tac is necessary or useful. I refactored the first grep into the Awk script for efficiency and aesthetic reasons.

To solve the general "my xargs lacks -r" problem, the gist of the solution is to convert

stuff | xargs -r cmd

into

var=$(stuff)
test "$var" && cmd $var

The unquoted $var will only work if it doesn't contain file names with spaces or other surprises; but then bare-bones xargs without the GNU extensions suffers from the same problem.

tripleee
  • 175,061
  • 34
  • 275
  • 318
3

The -r for xargs isn't working on OS X, since it's a GNU extension. As for workaround, you should specify some dummy file which can be parsed by xargs or to not call command when there are no arguments (which can be checked before).

Method using temporary file (or any other placeholder):

some_cmd ... | xargs svn revert $(mktemp)

Using condition in shell:

files=$(cd checkout_dir && ... | tac)
if [ -n "$files" ]; then
    echo $files | xargs && ...
fi

See also: Ignore empty result for xargs

Community
  • 1
  • 1
kenorb
  • 155,785
  • 88
  • 678
  • 743
1

Bash reimplementation of xargs dealing with the -r argument:

#!/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 $@
rcomblen
  • 4,579
  • 1
  • 27
  • 32