Search code examples
bashshellsox

Using space-separated arguments from a field in a tab-separated file


I'm writing a shell script intended to edit audio files using the sox command. I've been running into a strange problem I never encountered in bash scripting before: When defining space separated effects in sox, the command will work when that effect is written directly, but not when it's stored in a variable. This means the following works fine and without any issues:

sox ./test.in.wav ./test.out.wav delay 5

Yet for some reason the following will not work:

IFS='   ' # set IFS to only have a tab character because file is tab-separated
while read -r file effects text; do
  sox $file.in.wav $file.out.wav $effects
done <in.txt

...when its in.txt is created with:

printf '%s\t%s\t%s\n' "test" "delay 5" "other text here" >in.txt

The error indicates this is causing it to see the output file as another input.

sox FAIL formats: can't open input file `./output.wav': No such file or directory

I tried everything I could think of: Using quotation marks (sox "$file.in.wav" "$file.out.wav" "$effects"), echoing the variable in-line (sox $file.in.wav $file.out.wav $(echo $effects)), even escaping the space inside the variable (effects="delay\ 5"). Nothing seems to work, everything produces the error. Why does one command work but not the other, what am I missing and how do I solve it?


Solution

  • IFS does not only change the behavior of read; it also changes the behavior of unquoted expansions.

    In particular, unquoted expansions' content are split on characters found in IFS, before each element resulting from that split is expanded as a glob.

    Thus, if you want the space between delay and 5 to be used for word splitting, you need to have a regular space, not just a tab, in IFS. If you move your IFS assignment to be part of the same simple command as the read, as in IFS=$'\t' read -r file effects text; do, that will stop it from changing behavior in the rest of the script.


    However, it's not good practice to use unquoted expansions for word-splitting at all. Use an array instead. You can split your effects string into an array with:

    IFS=' ' read -r -a effects_arr <<<"$effects"
    

    ...and then run sox "$file.in.wav" "$file.out.wav" "${effects_arr[@]}" to expand each item in the array as a separate word.


    By contrast, if you need quotes/escapes/etc to be allowed in effects, see Reading quoted/escaped arguments correctly from a string