How to use wildcards in a xargs-command?

You were very close.

seq 15 -1 1 | xargs -I@ sh -c 'mv @_* @'

You need to delay the interpretation (expansion) of the * until after the @ substitution has occurred.  (But you already understood that that was the problem, right?)

I’ve been advised never to embed an unknown filename (or other substitution string) directly into a shell command line.  The above example is probably fairly safe, because you know what the strings are going to be — 15, 14, …, 3, 2 and 1.  But using the above example as a template for more complex commands can be dangerous.  A safer arrangement would be

seq 15 -1 1 | xargs -I@ sh -c 'mv -- "$1"_* "$1"' x-sh @

where x-sh is a semi-arbitrary string that will be used to label any error messages issued by the invoked shell.  This is equivalent to my first example, except, rather than embedding the strings (represented by @) directly into the shell command, it injects them by supplying the @ as an argument to the shell, and then referencing them as "$1".


P.S. You suggested running the seq command in reverse (seq 15 -1 1, which generates 1514, …, 3, 2, 1 rather than 1, 2, 3, …, 14, 15) and nobody mentioned it.  This would be an important part of the answer if your filenames were like 1foo.txt, 2bar.asc, and 13test.png, etc. (with various characters appearing after the number, rather than always _).  In that case, the command would be mv "$1"* "$1" (without the _), and, if you did 1 first, then the command mv 1* 1 would sweep up all the 10quick*, 11brown*, 12fox*, etc., files, along with the 1foo* files.  But

seq 1 15 | xargs -I@ sh -c 'mv -- "$1"_* "$1"' x-sh @

should be safe.

P.P.S. The seq command is not specified by POSIX.  Neither is brace expansion in the shell.  A POSIX-compliant solution can be constructed by combining grawity’s answer to this question with this other answer by Adam Katz:

i=1
while [ "$i" -le 15 ]
do
    mv -- "${i}"_* "$i"
    i=$((i+1))
done

P.P.P.S. It’s not critical when you know that the file names begin with alphanumeric characters (i.e., letters and digits), but, in more general cases, you should use -- between the command name and the arguments.  This improves the handling of filenames that begin with a dash.  The -- tells the command to treat the argument (the file name) as an argument.  Without it, such an argument might be treated as an option string.


Just don't use xargs for that. Use a for loop:

for i in $(seq 1 15); do
    mv ${i}_* $i
done

Even better is to use brace expansion instead of seq:

mkdir {1..15}

for i in {1..15}; do
    mv ${i}_* $i
done