Why?
The place holder/replace string e.g. {}
should get replaced after the xargs
command, even if improperly placed inside a single quoted Bash command string (which can be dangerous ... See the remarks part below for explanation) that will run in a sub-shell like e.g.:
$ printf '%s\n' 'line1' 'line2' 'line3' | xargs -I {} -r -n1 bash -c 'echo "This is {}"'
This is line1
This is line2
This is line3
Although, the proper and safer way is to pass it to the Bash script as a positional parameter like so:
$ printf '%s\n' 'line1' 'line2' 'line3' | xargs -I {} -r -n1 bash -c 'echo "This is $1"' bash {}
This is line1
This is line2
This is line3
That said, if you enclose the Bash script in a command substitution($( ... )
) syntax, then the command substitution part gets executed before the xargs
command i.e. before the place holder {}
is assigned any value and therefore, it will be processed as a literal {}
.
If you enable command traces in Bash like so:
$ set -x
Then run without command substitution:
$ printf '%s\n' 'line1' 'line2' 'line3' | xargs -I {} -r -n1 bash -c 'echo "This is {}"'
+ xargs -I '{}' -r -n1 bash -c 'echo "This is {}"'
+ printf '%s\n' line1 line2 line3
This is line1
This is line2
This is line3
And then, with command substitution:
$ printf '%s\n' 'line1' 'line2' 'line3' | xargs -I {} -r -n1 $(bash -c 'echo "This is {}"')
+ printf '%s\n' line1 line2 line3
++ bash -c 'echo "This is {}"'
+ xargs -I '{}' -r -n1 This is '{}'
xargs: This: No such file or directory
It should be obvious to you now that the xargs
command is trying to execute and pass arguments to the output of the command substitution, because command substitution gets expanded early on the command line in Bash.
How?
Although moving the command substitution part inside the Bash command string so it will be run in a sub-shell(Command substitution) from withing another sub-shell(Bash command string) from within anothr sub-shell(created for that part of the pipe line) ... Therefore, delaying its execution(away from the perspective of the current shell's command line parsing) until the place holder {}
has been assigned a value by xargs
... Although that might work, but I would rather not do it.
Not discussing your obvious parsing of ls
's output in your function and the probable piping of similar output by ls
as well which I'd rather not recommend ... Please see more explanation about that here.
What I would recommend instead is to not use a pipeline and not use xargs
, but rather use a shell loop to process your files like so:
for f in *; do
if [ -f "$f" ]; then
y=$(get_year "$f")
cp -v -- "$f" "${Dst}"/videos/"$y"
fi
done
Remarks
Probably Too Technical Notice: The place holder/replace string in xargs
option -I
e.g. {}
in your case is not actually expanded per-se i.e. by the shell, but rather replaced by xargs
and its replacement put in "the same" position on the command line itself by writing to standard output ... That can be observed with strace
like e.g. so:
$ printf '%s\n' 'line' | strace -f -e 'write' xargs -I {} echo {}
strace: Process 37373 attached
[pid 37373] write(1, "line\n", 5line
) = 5
[pid 37373] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=37373, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
... And that's why it could be dangerous to include the place holder/replace string as is in shell scripts as it will allow for command injection which could be malicious or destructive.
Compare for example:
$ printf '%s\n' '; seq 1 9' '; echo ~' | xargs -I {} bash -c 'echo {}'
1
2
3
4
5
6
7
8
9
/home/ubuntu
... where commands get executed e.g. seq 1 9
and shell expansion happens e.g. ~
expanded to your home directory ... Imagine what could happen if, for example, one of the injected commands happened to be ; rm -rf ~
Compare that with when the place holder/replace string is properly passed to the shell script as a positional parameter:
$ printf '%s\n' '; seq 1 9' '; echo ~' | xargs -I {} bash -c 'echo "$1"' bash {}
; seq 1 9
; echo ~
... where no injected commands get executed and no shell expansion happens.
The options -I
and -n
are mutually exclusive so you might not want to use them together although if the argument to -n
is 1
then probably no harm will be done ... Also -I
implies -L 1
so you might not need to use -n 1
at all.