Reputation: 101476
I am trying to write a bash script which does some processing on music files. Here is the script so far:
#!/bin/bash
SAVEIFS=$IFS
IFS=printf"\n\0"
find `pwd` -iname "*.mp3" -o -iname "*.flac" | while read f
do
echo "$f"
$arr=($(f))
exiftool "${arr[@]}"
done
IFS=$SAVEIFS
This fails with:
[johnd:/tmp/tunes] 2 $ ./test.sh
./test.sh: line 9: syntax error near unexpected token `$(f)'
./test.sh: line 9: ` $arr=($(f))'
[johnd:/tmp/tunes] 2 $
I have tried many different incantations, none of which have worked. The bottom line is I'm trying to call a command exiftool
, and one of the parameters of that command is a filename which may contain spaces. Above I'm trying to assign the filename $f
to an array and pass that array to exiftool
, but I'm having trouble with the construction of the array.
Immediate question is, how do I construct this array? But the deeper question is how, from within a bash script, do I call an external command with parameters which may contain spaces?
Upvotes: 1
Views: 10111
Reputation: 4708
You actually did have the call-with-possibly-space-containing-arguments syntax right (program "${args[@]}"
). There were several problems, though.
Firstly, $(foo)
executes a command. If you want a variable's value, use $foo
or ${foo}
.
Secondly, if you want to append something onto an array, the syntax is array+=(value)
(or, if that doesn't work, array=("${array[@]}" value)
).
Thirdly, please separate filenames with \0
whenever possible. Newlines are all well and good, but filenames can contain newlines.
Fourthly, read
takes the switch -d
, which can be used with an empty string ''
to specify \0
as the delimiter. This eliminates the need to mess around with IFS
.
Fifthly, be careful when piping into while
loops - this causes the loop to be executed in a subshell, preventing variable assignments inside it from taking effect outside. There is a way to get around this, however - instead of piping (command | while ... done
), use process substitution (while ... done < <(command)
).
Sixthly, watch your process substitutions - there's no need to use $(pwd)
as an argument to a command when .
will do. (Or if you really must have full paths, try quoting the pwd
call.)
The script, revised:
while read -r -d '' f; do
echo "$f" # For debugging?
arr+=("$f")
done < <(find . -iname "*.mp3" -o -iname "*.flac" -print0)
exiftool "${arr[@]}"
Leveraging find
's full capabilities:
find . -iname "*.mp3" -o -iname "*.flac" -exec exiftool {} +
# Much shorter!
So you need to save the output of exiftool
, manipulate it, then copy stuff? Try this:
while read -r -d '' f; do
echo "$f" # For debugging?
arr+=("$f")
done < <(find . -iname "*.mp3" -o -iname "*.flac" -print0)
# Warning: somewhat misleading syntax highlighting ahead
newfilename="$(exiftool "${arr[@]}")"
newfilename="$(manipulate "$newfilename")"
cp -- "$some_old_filename" "$newfilename"
You probably will need to change that last bit - I've never used exiftool
, so I don't know precisely what you're after (or how to do it), but that should be a start.
Upvotes: 5
Reputation: 247012
You can do this just with bash:
shopt -s globstar nullglob
a=( **/*.{mp3,flac} )
exiftool "${a[@]}"
This probably works too: exiftool **/*.{mp3,flac}
Upvotes: 2