bashdeclareifs

Why does declare reset the IFS in bash?


Why does this work to create an array out of the newline separated elements:

declare -a themes
IFS=$'\n' themes=($(plutil -extract 'Window Settings' xml1 -o - - <<< "$terminal_settings" | xmllint --xpath '/plist/dict/key/node()' -))

but this doesn't, instead making an array of space separated elements:

IFS=$'\n' declare -a themes=($(plutil -extract 'Window Settings' xml1 -o - - <<< "$terminal_settings" | xmllint --xpath '/plist/dict/key/node()' -))

Normally when IFS won't work for a specific line, it must be set before the line, then reset afterwards, but in this case setting it for the one line is fine, but if the line contains declare it doesn't work.


Solution

  • The thing that surprises you here is that declare is a command, and follows the same syntax rules as other commands.

    This is also true of local, typeset, etc.


    That said, insofar as your goal is to read lines into an array, array=( $(...) ) is the wrong tool for the job anyhow -- string splitting also invokes globbing behavior, so if you had a line containing only * it would be replaced with a list of filenames; shell flags like nullglob and globfail apply; etc.

    Supporting bash 3.2+, and also ensuring that read fails if the plutil | xmllint pipeline itself returns a nonzero exit status (you might want to use set -o pipefail to make that happen if plutil fails even should xmllint succeed):

    IFS=$'\n' read -r -d '' -a themes < <(
      plutil -extract 'Window Settings' xml1 -o - - <<< "$terminal_settings" \
        | xmllint --xpath '/plist/dict/key/node()' - \
        && printf '\0'
    )
    

    Terser, but limited to bash 4.0+, and not passing failures back to the parent:

    readarray -t themes <(
      plutil -extract 'Window Settings' xml1 -o - - <<< "$terminal_settings" \
        | xmllint --xpath '/plist/dict/key/node()' -
    )