Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

xargs sh -c skipping the first argument

I'm trying to write a script that uses find and xargs to archive old files in a large directory. Here is the line:

find /tmp/messages/ -mtime +9 -print0 | xargs -x -t -0 -n 1000 sh -c 'tar rPf /tmp/backup.tar "$@" && rm -f "$@"; sleep 1 && echo Finished one loop: $(date +"%T")'

The script mostly works but it skips the first file each time the commands run for 1000 files and I can't seem to figure out why.

Here is another example (I tried playing around with simpler commands to see what happens:

If I just use echo and xargs, I can run one command effectively:

$ echo a b c d e f| xargs -n 2 echo "$@"
a b
c d
e f
$ echo a b c d e f| xargs -n 3 echo "$@"
a b c
d e f

On the other hand, if I want to sleep every time I print each set, I add sh -c to add a string of commands:

$ echo a b c d e f| xargs -n 2 sh -c 'echo "$@"; sleep 1;'
b
d
f
$ echo a b c d e f| xargs -n 3 sh -c 'echo "$@"; sleep 1;'
b c
e f

See how the first item is skipped?

Any ideas on what could be going on here?

Thanks in advance! And let me know if you need any extra info, I'd be glad to provide it.

like image 356
Guillermo Mirandes Avatar asked Dec 08 '16 15:12

Guillermo Mirandes


2 Answers

First argument to sh -c or bash -c is the name of the script i.e. $0 which is not printed when you use $@:

Examples:

echo a b c d e f| xargs -n 3 bash -c 'echo "$0 $@"'
a b c
d e f

To fix this you can pass _ as dummy name of the script and then it should work:

echo a b c d e f| xargs -n 3 bash -c 'echo "$@"' _
a b c
d e f

It will work fine even with your sleep example:

echo a b c d e f| xargs -n 3 bash -c 'echo "$@"; sleep 1' _
a b c
d e f
like image 105
anubhava Avatar answered Oct 23 '22 11:10

anubhava


In the Shell Command language the @ special parameter

Expands to the positional parameters, starting from one.

The sh command is invoked in the following form:

sh -c[OPTIONS] command_string [command_name [argument...]]

From the documentation for the -c option:

Read commands from the command_string operand. Set the value of special parameter 0 (see Special Parameters) from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands.

For example, in the following command:

sh -c 'tar czvf arch.tar.gz $@' a b c
  • command_string is 'tar czvf arch.tar.gz $@',
  • command_name is a,
  • the first argument is b
  • the second argument is c

Thus, within the command string $0 will expand to a, $1 to b, $2 to c, and the $@ special parameter will expand to b c.

So if you want to use the command_name within the command string, refer to the special parameter zero, i.e. $0.

like image 21
Ruslan Osmanov Avatar answered Oct 23 '22 12:10

Ruslan Osmanov