Search code examples
bashmacosshellglob

Why does `cat` list files instead of content of file here?


I tried to do something tricky today with bash scripting, which made me question my knowledge of bash scripting.

I have the following script called get_ftypes.sh, where the first input argument is a file containing file globs:

for ftype in `cat $1`
do
    echo "this is ftype $ftype"
done

For example, the script would be called like this get_ftypes.sh file_types, and file_types would contain something like this:

*.txt
*.sh

I would expect the echo to print each line in the file, which in this example would be *.txt, *.sh, etc. But, instead it expands the globbing, *, and it echos the actual file names, instead of the globb as I would expect.

Any reason for this behavior? I cannot figure out why. Thank you.


Solution

  • On the line for ftype in `cat $1`, the shell performs both word splitting and pathname expansion. If you don't want that, use a while loop:

    while read -r ftype
    do
        echo "this is ftype $ftype"
    done <"$1"
    

    This loop reads one line at a time from the file $1 and, while leading and trailing whitespace are removed from each line, no expansions are performed. (If you want to keep the leading and trailing whitespace, use while IFS= read -r ftype).

    Typically, for loops are useful when you are looping over items that are already shell-defined variables, like for x in "$@". If you are reading something in from an external command or file, you typically want a while read loop.

    Alternative not using shell

    When processing files line-by-line, the goal can often be accomplished more efficiently using sed or awk. As an example using awk, the above loop simplifies to:

    $ awk '{print "this is ftype " $0}' filetypes 
    this is ftype *.txt
    this is ftype *.sh