Alex
Alex

Reputation: 158

What is the equivalent to xargs -r under OsX

Are they any equivalent under OSX to the xargs -r under Linux ? I'm trying to find a way to interupt a pipe if there's no data.

For instance imagine you do the following:

touch test
cat test | xargs -r echo "content: "

That doesn't yield any result because xargs interrupts the pipe.

Is there either some hidden xargs option or something else to achieve the same result under OSX?

Upvotes: 12

Views: 6576

Answers (8)

TomOnTime
TomOnTime

Reputation: 4477

There is no standard way to determine if the xargs you are running is GNU or not. I set $gnuargs to either "true" or "false" and then have a function that replaces xargs and does the right thing.

On Linux, FreeBSD and MacOS this script works for me. The POSIX standard for xargs mandates that the command be executed once, even if there are no arguments. FreeBSD and MacOS X violate this rule, thus don't need "-r". GNU finds it annoying, and adds -r. This script does the right thing and can be enhanced if you find a version of Unix that does it some other way.

#!/bin/bash

gnuxargs=$(xargs --version 2>&1 |grep -s GNU >/dev/null && echo true || echo false)

function portable_xargs_r() {
  if $gnuxargs ; then
    cat - | xargs -r "$@"
  else
    cat - | xargs "$@"
  fi
}

echo 'this' > foo
echo '=== Expect one line'
portable_xargs_r <foo echo "content: "
echo '=== DONE.'

cat </dev/null > foo
echo '=== Expect zero lines'
portable_xargs_r <foo echo "content: "
echo '=== DONE.'

Upvotes: 2

KamilCuk
KamilCuk

Reputation: 141155

You can test if the stream has any content:

cat test | { if IFS= read -r tmp; then { printf "%s\n" "$tmp"; cat; } | xargs echo "content: "; fi; }
#                                                                                               ^^^ - otherwise just do nothing
#                                                                       ^^^^^^^^^^^^^^^^^^^^^^^ - to xargs
#                                                              ^^^ - and the rest of input
#                                      ^^^^^^^^^^^^^^^^^^^^^^ - redirect first line
#            ^^^^^^^^^^^^^^^^^^^ - try reading anything

# or with a function
# even TODO: add the check of `portable_xargs_r` in the other answer and call `xargs -r` when available.
xargs_r() {
   if IFS= read -r tmp; then
       { printf "%s\n" "$tmp"; cat; } | xargs "$@"
   fi
}
cat test | xargs_r echo "content: "

This method runs the check inside the pipe inside the subshell, so it effectively can be used in a complicated pipe setup.

Upvotes: 0

Stephane Chazelas
Stephane Chazelas

Reputation: 6239

with POSIX xargs¹, to avoid running the-command when the input is empty, you could use moreutils's ifne (for if not empty):

... | ifne xargs ... the-command ...

Or use a sh wrapper that checks the number of arguments:

... | xargs ... sh -c '[ "$#" -eq 0 ] || exec the-command ... "$@"' sh

¹ though one can hardly use xargs POSIXly as it doesn't support -0, has unspecified behaviour when the input is non-text (like for filenames which on most systems are not guaranteed to be text except in the POSIX locale), parses its input in a very arcane way and that is locale-dependant, and doesn't give any guarantee if any word is more than 255 bytes long!

Upvotes: 1

TomOnTime
TomOnTime

Reputation: 4477

A typical use case looks like:

find . -print0 | xargs -r -0 grep PATTERN

Some versions of xargs do not have an -r flag. In that case, you can supply /dev/null as the first filename so that grep is never handed an empty list of filenames. Since the pattern will never be found in /dev/null, this won't affect the output:

find . -print0 | xargs  -0 grep PATTERN /dev/null

Upvotes: 0

Jonathan Leffler
Jonathan Leffler

Reputation: 754090

The POSIX standard for xargs mandates that the command be executed once, even if there are no arguments. This is a nuisance, which is why GNU xargs has the -r option. Unfortunately, neither BSD (MacOS X) nor the other mainstream Unix versions (AIX, HP-UX, Solaris) support it.

If it is crucial to you, obtain and install GNU xargs somewhere that your environment will find it, without affecting the system (so don't replace /usr/bin/xargs unless you're a braver man than I am — but /usr/local/bin/xargs might be OK, or $HOME/bin/xargs, or …).

Upvotes: 3

tripleee
tripleee

Reputation: 189487

Here's a quick and dirty xargs-r using a temporary file.

#!/bin/sh
t=$(mktemp -t xargsrXXXXXXXXX) || exit
trap 'rm -f $t' EXIT HUP INT TERM
cat >"$t"
test -s "$t" || exit
exec xargs "$@" <"$t"

Upvotes: 1

TomOnTime
TomOnTime

Reputation: 4477

You could make sure that the input always has at least one line. This may not always be possible, but you'd be surprised how many creative ways this can be done.

Upvotes: 0

choroba
choroba

Reputation: 241918

You can use test or [:

if [ -s test ] ; then cat test | xargs echo content: ; fi

Upvotes: 2

Related Questions