The effect of “${(@f)…}” in Zsh

variable substitutionzsh

I ran into a Zsh script and wanted to know its meaning. In the scripts below there is ${$(@f)$(egrep "$2","$file")} expression. From what I searched, @ is to use represent all positional parameters, but then the postfix letter f is not mentioned anywhere in above script and I couldn't find any online material saying its special meaning.

#!/usr/bin/env zsh

tmp_file="/tmp/_unsorted_find_n_grep"
echo "--- find <$2> in <$1>" > $tmp_file

find . -follow -iname "$1" | while read file
do
    timestamp=$(ls -l --time-style=+"%Y-%m-%d %H:%M:%S" "$file" | gawk '{print $6, $7}')
    linestack=${(@f)$(egrep "$2" "$file")}
    for line in $linestack 
do
    echo "$timestamp $file: $line" >> $tmp_file
done
done

cat $tmp_file | sort
rm $tmp_file

Best Answer

The character inside the parentheses are parameter expansion flags. They can be used around a variable substitution or a command substitution, e.g.

${(@f)SOME_VARIABLE}
${(@f)${(xyz)SOME_VARIABLE}}
${(@f)$(some-command)}

The flag f splits the result of the expansion at newlines. The flag @ ensures that the resulting array is expanded as multiple words; oddly, it only has effect inside double quotes, where it acts as a generalization of "$@".

Usually, these flags are used like this:

lines=("${(@f)$(egrep "$2" "$file")}")

This way lines becomes an array where each element is one line of output from egrep.

Here, the expansion is not in a context that allows multiple words, so the @ flag has no effect. The effect of the f flag is somewhat unintuitive: it forces an array context, so the IFS-separated words in the egrep output are stored in a temporary array which is then joined with the first character of IFS.

linestack=${(@f)$(egrep "$2" "$file")}

Contrast:

$ zsh -c 'a=${(f)$(echo hello world; echo wibble)}; print -lr $#a $a'
18
hello world wibble
$ zsh -c 'IFS=:$IFS; a=${(f)$(echo hello world; echo wibble)}; print -lr $#a $a'
18
hello:world:wibble
$ zsh -c 'IFS=:; a=${(f)$(echo hello world; echo wibble)}; print -lr $#a $a'
18
hello world
wibble

$ zsh -c 'a=(${(f)$(echo hello world; echo wibble)}); print -lr $#a $a' 
1
hello world wibble
$ zsh -c 'a=("${(@f)$(echo hello world; echo wibble)}"); print -lr $#a $a'
2
hello world
wibble
Related Question