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?

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • answer for the space-in-the-arg problem: http://stackoverflow.com/questions/905891/passing-argument-containing-space-in-shell-script (simply enclose "the spaced arg" into double quotes) – clt60 May 03 '13 at 12:18
  • 1
    How about -exec exiftool {} ? – Szocske May 03 '13 at 12:22

2 Answers2

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.

  • 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`? – John Dibling May 03 '13 at 13:12
  • @JohnDibling Sounds possible the long way (first script). I'll edit something in. – michaelb958--GoFundMonica May 03 '13 at 14:26
2

You can do this just with bash:

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

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

glenn jackman
  • 238,783
  • 38
  • 220
  • 352