21

I want the following behavior without having to explicitly specify it with options:

xargs -d '\n'

Unlike with most commands, you can't just use an alias because pipes don't recognize aliases (as a side-note, why is it designed this way?). I also tried creating my own ~/bin/xargs script but I think it's not as simple as reading "$@" as a string inside the script.

Any suggestions how to make the delimiter a newline by default? I don't want to get a bunch of errors when I have a space in the path (and using find ... -print0 | xargs -0 has other unwanted effects).

UPDATE

My shell script attempt is this:

/usr/local/bin/xargs -d '\n' "$@"
Sridhar Sarnobat
  • 25,183
  • 12
  • 93
  • 106
  • 1
    Aliases work with pipes for me. But when you run into the limitations of aliases (like they can't process arguments), just use a shell function instead. – Barmar Oct 06 '13 at 01:42
  • 2
    AFAIK, there's no way to change xargs's default behavior. Please show the `xargs` script you tried to write, if you want help fixing it. – Barmar Oct 06 '13 at 01:44
  • Thanks for the suggestions. I hadn't explored the option of a shell function. That sounds interesting. I'm not in favor of tr since I could just as well use the -d flag. So far the best option for me is to add the option to my xargs key binding. I'll show my script shortly... – Sridhar Sarnobat Oct 06 '13 at 01:46
  • Do you want to know why that shell script doesn't work? Or are you satisified with using a shell function, as @Barmar suggests. (I haven't had any problem piping to aliases either. What version of bash are you using? -- or are you using some other shell?) – rici Oct 06 '13 at 01:59
  • 1
    Shell version: zsh 4.3.12 (i386-apple-darwin11.2.0). Actually I think I get the output I want from executing "grep path/with/whitespace myString" but after the result it shows "grep: path/with/whitespace: No such file or directory". It's not a huge problem, most of the time I use xargs with a keybinding. But it's something that has bothered me for years. I'm not familiar with shell functions so would like to hear other suggestions. – Sridhar Sarnobat Oct 06 '13 at 02:03
  • @Sridhar-Sarnobat: I have zsh 5.0, and piping to an alias works just fine. Shell functions are really easy; if you've been using shells for years, it's definitely time you took a look at them. – rici Oct 06 '13 at 02:31
  • Thanks Rici. I will invest some time learning them. – Sridhar Sarnobat Oct 06 '13 at 06:41

3 Answers3

18

I tested xargs -d '\n' -n1 echo on the command line and it worked as advertised; breaking the input up into single lines and echoing it.

You could be being bitten by assumptions regarding shell escaping during variable assignment. For instance, if your shell script is similar to this:

CMD="xargs -d '\n' -n1 echo"
cat inputfile | $CMD

then an error occurs, because the double-quotes around the entire CMD string preserve both the single-quotes and the backslash in the delimiter.

If you are enclosing the xargs command within quotes, you could change it to the following (no single-quotes)

CMD="xargs -d \n -n1 echo"
cat inputfile | $CMD

and it will likely function as you need.

Tested on CentOS 6.4, xargs version 4.4.2

11

If your version of xargs doesn't support setting a different delimiter (like the one on a 2011 FreeBSD server...), you can use tr to turn your delimiters into something xargs will work with. You might need to use a few translations before and after if your input has characters that your xargs considers delimiters (such as spaces) or you could simply translate your delimiters to NULL/0x0 and pass the -0 argument to xargs.

Here is an example of using xargs to find any file in $PATH that could be a possible shell to use (the reason I'm here, then I came up with another answer):
echo $PATH | tr ':' '\0' | xargs -0 ls | grep sh | sort

Chinoto Vokro
  • 928
  • 8
  • 17
2

I have ~/bin in my PATH and keep a short script with my favorite defaults accessible by all shells on my system (at different times I use Dash, Bash, and Fish). It's tiny:

#!/usr/bin/env sh
exec xargs -rd\\n "$@"

The script is called x to avoid conflicts with scripts expecting the standard xargs defaults. If you try to replace xargs itself, which I don't recommend, make sure your ~/bin appears higher in your PATH than the system xargs. which -a xargs tells me that the only xargs exists at /usr/bin/xargs in my system so I know my home directory, /home/stephen/bin, must appear before it like so:

$ echo "$PATH"
/home/stephen/bin:...:/usr/bin:...

Either way, as a script accessible by all programs, this means you can do things like find|x grep and sh -c 'x ...'.

If you use Bash and prefer an alias, you can also just use:

alias x=xargs -rd\\n\ # \n delim, don't run on empty in, + alias expansion

Note the trailing space for alias expansion. This lets you chain aliases. For example, if in addition to the above alias I had an alias for grep called g, the following would work:

# extended regex, skip binaries, devices, sockets, & dirs, colored, & line
# -buffered. use a non-canonical alias instead of GREP_OPTIONS which may wreck
# assuming scripts
alias g='grep -EID skip -d skip --color=auto --line-buffered'

$ find|x g foo

The x / xargs script approach can't do this effectively by itself.

Since I switch between shells and use one PC mostly, I keep the handful of aliases I need as separate scripts in ~/bin and shell agnostic aliases in a helper script, ~/.sh_aliases, which is sourced by ~/.shrc, ~/.bashrc, and ~/.config/fish/config.fish as they all support a Bash-like alias syntax. If I worked on multiple PCs regularly, I would probably try to consolidate these into ~/.bashrc instead.

Stephen Niedzielski
  • 2,497
  • 1
  • 28
  • 34