Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make xargs execute the command once for each line of input

Tags:

xargs

How can I make xargs execute the command exactly once for each line of input given? It's default behavior is to chunk the lines and execute the command once, passing multiple lines to each instance.

From http://en.wikipedia.org/wiki/Xargs:

find /path -type f -print0 | xargs -0 rm

In this example, find feeds the input of xargs with a long list of file names. xargs then splits this list into sublists and calls rm once for every sublist. This is more efficient than this functionally equivalent version:

find /path -type f -exec rm '{}' \;

I know that find has the "exec" flag. I am just quoting an illustrative example from another resource.

like image 604
readonly Avatar asked Oct 13 '08 22:10

readonly


People also ask

How do I run multiple commands in xargs?

To run multiple commands with xargs , use the -I option. It works by defining a replace-str after the -I option and all occurrences of the replace-str are replaced with the argument passed to xargs.

How does xargs command work?

The xargs command builds and executes commands provided through the standard input. It takes the input and converts it into a command argument for another command. This feature is particularly useful in file management, where xargs is used in combination with rm , cp , mkdir , and other similar commands.

Can we use xargs and standard input?

xargs is a Unix command which can be used to build and execute commands from standard input.

How do you pass arguments to xargs?

The -c flag to sh only accepts one argument while xargs is splitting the arguments on whitespace - that's why the double quoting works (one level to make it a single word for the shell, one for xargs).


2 Answers

The following will only work if you do not have spaces in your input:

xargs -L 1 xargs --max-lines=1 # synonym for the -L option 

from the man page:

-L max-lines           Use at most max-lines nonblank input lines per command line.           Trailing blanks cause an input line to be logically continued  on           the next input line.  Implies -x. 
like image 78
Draemon Avatar answered Nov 08 '22 23:11

Draemon


It seems to me all existing answers on this page are wrong, including the one marked as correct. That stems from the fact that the question is ambiguously worded.

Summary:   If you want to execute the command "exactly once for each line of input," passing the entire line (without newline) to the command as a single argument, then this is the best UNIX-compatible way to do it:

... | tr '\n' '\0' | xargs -0 -n1 ... 

If you are using GNU xargs and don't need to be compatible with all other UNIX's (FreeBSD, Mac OS X, etc.) then you can use the GNU-specific option -d:

... | xargs -d\\n -n1 ... 

Now for the long explanation…


There are two issues to take into account when using xargs:

  1. how does it split the input into "arguments"; and
  2. how many arguments to pass the child command at a time.

To test xargs' behavior, we need an utility that shows how many times it's being executed and with how many arguments. I don't know if there is a standard utility to do that, but we can code it quite easily in bash:

#!/bin/bash echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo 

Assuming you save it as show in your current directory and make it executable, here is how it works:

$ ./show one two 'three and four' -> "one" "two" "three and four"  

Now, if the original question is really about point 2. above (as I think it is, after reading it a few times over) and it is to be read like this (changes in bold):

How can I make xargs execute the command exactly once for each argument of input given? Its default behavior is to chunk the input into arguments and execute the command as few times as possible, passing multiple arguments to each instance.

then the answer is -n 1.

Let's compare xargs' default behavior, which splits the input around whitespace and calls the command as few times as possible:

$ echo one two 'three and four' | xargs ./show  -> "one" "two" "three" "and" "four"  

and its behavior with -n 1:

$ echo one two 'three and four' | xargs -n 1 ./show  -> "one"  -> "two"  -> "three"  -> "and"  -> "four"  

If, on the other hand, the original question was about point 1. input splitting and it was to be read like this (many people coming here seem to think that's the case, or are confusing the two issues):

How can I make xargs execute the command with exactly one argument for each line of input given? Its default behavior is to chunk the lines around whitespace.

then the answer is more subtle.

One would think that -L 1 could be of help, but it turns out it doesn't change argument parsing. It only executes the command once for each input line, with as many arguments as were there on that input line:

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show  -> "one"  -> "two"  -> "three" "and" "four"  

Not only that, but if a line ends with whitespace, it is appended to the next:

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show  -> "one" "two"  -> "three" "and" "four"  

Clearly, -L is not about changing the way xargs splits the input into arguments.

The only argument that does so in a cross-platform fashion (excluding GNU extensions) is -0, which splits the input around NUL bytes.

Then, it's just a matter of translating newlines to NUL with the help of tr:

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show  -> "one " "two" "three and four"  

Now the argument parsing looks all right, including the trailing whitespace.

Finally, if you combine this technique with -n 1, you get exactly one command execution per input line, whatever input you have, which may be yet another way to look at the original question (possibly the most intuitive, given the title):

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show  -> "one "  -> "two"  -> "three and four"  

As mentioned above, if you are using GNU xargs you can replace the tr with the GNU-specific option -d:

$ echo $'one \ntwo\nthree and four' | xargs -d\\n -n1 ./show  -> "one "  -> "two"  -> "three and four"  
like image 45
Tobia Avatar answered Nov 08 '22 23:11

Tobia