1

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?

2
  • answer for the space-in-the-arg problem: stackoverflow.com/questions/905891/… (simply enclose "the spaced arg" into double quotes) Commented May 3, 2013 at 12:18
  • 1
    How about -exec exiftool {} ? Commented May 3, 2013 at 12:22

2 Answers 2

5

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.)

tl;dr

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[@]}"

Another way

Leveraging find's full capabilities:

find . -iname "*.mp3" -o -iname "*.flac" -exec exiftool {} +
# Much shorter!

Edit 1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

This accomplishes what I set out above, so I'll accept. Ultimately I need to do more than just call exiftoo though -- I need to save the return from exiftool to a variable, manipulate it with some logic to produce a filename, and then copy the original file to a new location using the new filename. Can I use exec to do more than one thing, ie, more than just call exiftool?
@JohnDibling Sounds possible the long way (first script). I'll edit something in.
2

You can do this just with bash:

shopt -s globstar nullglob
a=( **/*.{mp3,flac} )
exiftool "${a[@]}"

This probably works too: exiftool **/*.{mp3,flac}

1 Comment

Note that globstar requires bash 4. Some distributions may still have an earlier version.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.