1

I'd like to run a shell command on each line taken from STDIN.

In this case, I'd like to run xargs mv. For example, given two lines:

mfoo foo
mbar bar

I'd like to run:

xargs mv mfoo foo
xargs mv mbar bar

I've tried the following strategies with ruby, awk, and xargs. However, I'm doing it wrong:

Just xargs:

$ echo "mbar bar\nmbaz baz" | xargs mv
usage: mv [-f | -i | -n] [-v] source target
       mv [-f | -i | -n] [-v] source ... directory

Through awk:

$ echo "mbar bar\nmbaz baz" | awk '{ system("xargs $0") }'

Through ruby:

$ echo "mbar bar\nmbaz baz" | ruby -ne '`xargs mv`'
$ ls
cat  foo  mbar mbaz

I have some questions:

  • How do I do what I'm trying to do?
  • What is wrong with each of my attempts?
  • Is there a better way to "think about" what I'm trying to do?

I'm especially confused that my xargs attempt isn't working because the following works:

$ echo "foo\nbar" | xargs touch
$ ls
bar foo
mbigras
  • 3,100
  • 1
    Why do you want to use xargs? Try just running mv instead of xargs in your AWK and Ruby scripts. Then ponder what happens with filenames containing “special” characters (space…). – Stephen Kitt Apr 09 '17 at 22:07
  • What is it that you're wanting xargs to give you? – Michael Homer Apr 09 '17 at 22:17
  • That is, what do you want to happen by getting xargs mv mfoo foo and xargs mv mbar bar to run? – Michael Homer Apr 09 '17 at 22:18
  • @MichaelHomer I was thinking it might rename the files. – mbigras Apr 09 '17 at 22:20
  • 1
    I think what’s surprising is that you’re persisting in attempting to build an incorrect solution to your problem. It might be a worthwhile learning exercise, I can’t judge that. Have you read the xargs documentation? – Stephen Kitt Apr 09 '17 at 22:22
  • xargs mv mfoo foo will wait for input and run mv mfoo foo $x_1 $x_2 $x_3... for every line $x_n of input it gets. Is that what you wanted? – Michael Homer Apr 09 '17 at 22:22
  • @StephenKitt my intent wasn't too continue with my other question, (which is why it isn't a comment). My intent was to learn about running shell commands on each line of input taken from stdin and use this as a specific example. Is it a bad strategy? Can you recommend a way I can improve the question? Honestly, so far I've learned a lot from this question, so it doesn't seem bad for me. On my end a question that illustrates a fundamental lack of understanding of the "right" way to do something seems to me like a great opportunity to explain the "right" way to do it. – mbigras Apr 09 '17 at 23:09
  • echo "mbar bar\nmbaz baz" | awk '{ system("xargs mv " $0) }' ought to generate the xargs commands you requested. – Mark Plotnick Apr 10 '17 at 00:03
  • @MarkPlotnick it's doesn't fail but it also doesn't successfully rename the files for me. – mbigras Apr 10 '17 at 00:04
  • I found out why! xargs needs to have the args piped into it. The following will work: echo "mbar bar\nmbaz baz" | awk '{ system("mv " $0) }' notice the absence of xargs – mbigras Apr 10 '17 at 01:02
  • We can’t guess what your intent was. We often get chains of questions here which seem to pursue bad ideas, more often that questions from people genuinely trying to understand what’s going on. Re your “I found out why” just above, did you see my very first comment? – Stephen Kitt Apr 10 '17 at 04:46
  • I'm reading the awk programming language and the UNIX programming environment, I'm really excited about it and eager to learn more, I love how Brian uses specific examples to articulate more general concepts and has a fun, humble and non grouchy attitude. So I'm also asking specific questions. Do you think my questions are bad? How could they be improved if you do? Your comments are cryptic and confusing. If you're answering questions every day and getting burned out that makes sense, if you have something specific you think I can improve upon I'm happy to hear it also. – mbigras Apr 10 '17 at 05:12

4 Answers4

3
echo "mbar bar\nmbaz baz" | xargs mv

With xargs you should use the -t option to see what's going on. So in your above case if we were to invoke xargs with -t, what do we see:

mv mbar bar mbaz baz

So obviously it's not correct. What happened was that xargs like a hungry crocodile, ate all the args fed to it via the pipe by echo. So you need a way to limit the release of arguments to the croc. And since you requested on a per-line basis, then what you need is the -l or the -L for POSIX option.

echo "mbar bar\nmbaz baz" | xargs -l -t mv

POSIX-ly way:

echo "mbar bar\nmbaz baz" | xargs -L 1 -t mv

mv mbar bar
mv mbaz baz

And this is what you wanted. H.T.H

  • Bravo! Success on bsd xargs on macOS 10.12.3. From the man page: -L _number_: Call utility for every number lines read. – mbigras Apr 10 '17 at 00:09
  • You can also combine -p with -t and xargs will prompt user for confirmation before execution of each command according to man pages. – George Vasiliou Apr 10 '17 at 00:21
  • Do you know if there is another way to dry-run with the -t option? I've tried a strategy mentioned in another answer by using echo but it doesn't work if you're trying to dry-run echo itself :) – mbigras Apr 10 '17 at 00:26
  • @mbigras Remove the -t when using echo. And since echo is harmless so in a way it's its own dry-run :-) Or you may remove echo as well then xargs will list the arguments in the order it ate. –  Apr 10 '17 at 00:28
  • Haha, of course, was trying too many things :) That works for me. Also as mentioned in your other answer. If you don't mind adding a section on POSIX will be happy to accept your answer! – mbigras Apr 10 '17 at 00:32
  • Again, you should only do this if you don’t care about your data. – Stephen Kitt Apr 10 '17 at 04:48
  • @StephenKitt I would like to follow the case that this xargs exercise will break. I created a file with space in name: touch "my foo". Then i tried the solution advised in this answer with proper quoting: echo "mbar bar\n'my foo' foo" | xargs -l -t mv and works ok. Offcourse without quoting around my foo the mv breaks. Is that the case you talk about? – George Vasiliou Apr 10 '17 at 07:12
  • @George amongst others, yes. You’d need to handle any special character that’s allowed in a filename (including single and double quotes, newlines...). The for loop using shell globbing works fine, getting all that right with xargs is a lot harder (unless you can use the null character as a separator throughout). – Stephen Kitt Apr 10 '17 at 07:21
  • @Stephen OK - Now thing are more clear why this xargs mv is not a good idea to handle real files. Every special char needs special treatment to ensure the expected results, something that we do not need to worry using globbing methods or null separation techniques – George Vasiliou Apr 10 '17 at 07:41
2

You can go this way:

echo -e "foo bar\ndoo dar" | xargs -n 2 mv

To read from file:

cat lines.txt | xargs -n 2 mv

or

xargs -a lines.txt -n 2 mv
ddnomad
  • 1,978
  • 2
  • 16
  • 31
0

xargs might not behave how you expect. See @MichaelHomer's comment to this question:

xargs mv mfoo foo will wait for input and run mv mfoo foo $x_1 $x_2 $x_3... for every line $x_n of input it gets. @MichaelHomer

This can be confirmed by reading the xargs man page:

DESCRIPTION
The xargs utility reads space, tab, newline and end-of-file delimited strings from the standard input and executes utility with the strings as arguments.

In practice, it seems like xargs will ignore newlines when feeding args to the command line utility given as it's first arg. For example, notice what happens to the newline:

$ echo "foo bar\ncat dog"
foo bar
cat dog
$ echo "foo bar\ncat dog" | xargs echo
foo bar cat dog

This behavior can be controlled with the -n flag:

-n number

Set the maximum number of arguments taken from standard input for
each invocation of utility.

Going back to the previous example if we wanted xargs take only 2 args then quit then we can use @ddnomad's approach:

$ echo "foo bar\ncat dog" | xargs -n 2 echo
foo bar
cat dog

Also, as shown in the comments in Rakesh Sharma's answer, on BSD xargs you can specify the number of lines to be read with the -L flag. From the man page:

-L number

Call utility for every number lines read. If EOF is reached and
fewer lines have been read than number then utility will be called with the available lines.

Equally helpful for testing is the -t switch:

Revisiting our example, both of the followings will work; however, the second example is better because it's what was intending in this case:

$ echo "foo bar\ncat dog" | xargs -n 2 echo
foo bar
cat dog
$ echo "foo bar\ncat dog" | xargs -L 1 echo
foo bar
cat dog

Note from the man page:

The -L and -n options are mutually exclusive; the last one given will be used.

mbigras
  • 3,100
  • This seems to me to be a summary of the existing answers; you may self-answer, but please make sure you're adding something new. – Jeff Schaller Apr 10 '17 at 01:47
  • Do any of the other answers include references to the manual and notes for avoiding confusion? – mbigras Apr 10 '17 at 01:57
0

There is a very simple solution that does not use xargs.
Define this function:

$ mv2() { while (($# >1 )); do echo mv "$1" "$2"; shift 2; done; }

Then, just call it with the list of filenames needed (no newlines required):

$ mv2 mbar bar mbaz baz
mv mbar bar
mv mbaz baz

Remove the echo to make the function really work.