I'm new to bash and I'm having problems with using variables in xargs I think.

imanuel@orangepipc:~$ find /media/et10000 -name '*' -exec file {} \; | grep -o -P '^.+: \w+ image' | cut -d':' -f1 | xargs -I {} sh -c "fname="{}"; echo 'filename:'; echo $fname; filebase=$(basename $fname); filenew=${fname:14}; mkdir --parents /media/newhdd/photos$(dirname $fnamenew); cp -dv $fname /media/newhdd/photos$fnamenew; ln -s /media/newhdd/photos/year/$(date +%Y)/$fnamebase /media/newhdd/photos$fnamenew"

I get this error for every photo until I press ^C.


mkdir: cannot create directory ‘/home/imanuel/photos’: File exists
cp: missing destination file operand after ‘/home/imanuel/photos’
Try 'cp --help' for more information.
ln: failed to create symbolic link ‘/home/imanuel/photos/2016’: File exists

So the problem is that the fname variable doesn't seem to get set, whatever I try. I tried putting double quotes around {}, around $fname and replace the outer two double quotes with '.


I am not sure while you using xarg but there might be many ways to Rome as usual. here my approach for example:


SRCDIR="$HOME/Pictures/."     # should be : /media/et10000
DST="."                                       # should be : /media/newhdd/photos
find ${SRCDIR} -type f -exec file {} \; | grep -o -P '^.+: \w+ image' | cut -d':' -f1|while read fname
 echo "FILE:    ${fname}"
 filebase=$(dirname $fname);
 filenew=$(basename "$fname");
 echo "filenew: ${filenew}"
 ## place here all the mkdir, copy and link BUT take care that
 ## white spaces and other chars needs to be masked - so place your
 ## variable into this exact way: "${varname}" like
 ## ln -s "/media/newhdd/photos/year/$(date +%Y)/${fnamebase}"
 echo "---"

The OP uses double quotes for the argument to sh -c, which causes the parent shell to do variable and command substitutions. E.g., "...; echo $fname; ..." will be substituted by the parent shell into "...; echo ; ..." because the parent shell doesn't know $fname.

If you insist on using xargs (instead of while read -r line), I suggest using single quotes for the outter-most quote level, double quotes for the next quote level, then \" for the third level if needed.


I fixed the all-in-one command. And then I turned it into bash script. Any suggestions on making it more readable are welcome though. :D

find "$1" -name '*' -exec file {} \; | grep -o -P '^.+: \w+ image' | cut -d':' -f1 | xargs -I {} sh -c "fname={}; filebase=\$(basename \$fname); fnamenew=\${fname#$1}; mkdir --parents /media/newhdd/photos\$(dirname \$fnamenew); cp -dv \$fname /media/newhdd/photos\$fnamenew; mkdir --parents /media/newhdd/photos/year/\$(date +%Y); ln -fs /media/newhdd/photos\$fnamenew /media/newhdd/photos/year/\$(date +%Y)/\$fnamebase; mkdir --parents /media/newhdd/photos/year/\$(date +%Y/%m-%B); ln -fs /media/newhdd/photos\$fnamenew /media/newhdd/photos/year/\$(date +%Y/%m-%B)/\$fnamebase;"

The main problems with the script were these:

  • I didn't escape the dollar signs, so I replaced every $ with \$.
  • I accidentally typed filenew instead of fnamenew and the substring functionality wasn't available in Dash (Debian Almquist Shell), so I changed filenew=\${fname:14}; into fnamenew=\${fname#$1};.
  • I originally put double quotes around {}, but that was a typo. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.