How to pass a list of files with a particular extension in a for loop in bash

2.1k views Asked by At

First of all, hi to everyone, that's my first post here. I swear I have checked the site for similar questions to avoid the "double post about same argument" issue but none of them answered exactly to my question. The problem is that in the code below I always get the "There are no files with this extension" message when I call the script passing it an extension as first argument.

#!/bin/bash
if [ "$1" ];
    then
        file=*."$1";
        if [ -f "$file" ];
            then
                for i in "$file";
                [...do something with the each file using "$i" like echo "$i"]
            else
                echo "There are no files with this extension";
        fi;
     else
         echo "You have to pass an extension"
     fi;

I tried using the double parenthesis, using and not using the quotes in the nested if, using *."$1" directly in the if, but none of this solution worked.

1

There are 1 answers

3
larsks On BEST ANSWER

One problem is that you're not quoting a variable when you first assign a value to file. In this statement:

    file=*."$1";

The * will be interpreted by the shell, so for example if you passed in .py on the command line, file might end up with the value file1.py file2.py, which will throw off your file existence test later on.

Another problem, as @sideshowbarker points out, is that you can't use wildcards with the [ -f ... ].

Another variable quoting issue is that quoting inhibits wildcard expansion, such that even without the file existence test, if $file is, e.g., *.txt, then this:

for x in "$file"; do ...

Will loop over a single argument with the literal value *.txt, while this:

for x in $file; do ...

Will loop over all files that end with a .txt extension (unless there are none, in which case it will loop once with $x set to the literal value *.txt).

Typically, you would write your script to expect a list of arguments, and allow the user to call it like myscript *.txt...that is, leave wildcard handling to the interactive shell, and just let your script process a list of arguments. Then it becomes simply:

for i in "$@"; do
  echo do something with this file named "$x"
done

If you really want to handle the wildcard expansion in your script, something like this might work:

#!/bin/bash
if [ "$1" ];
    then
        ext="$1"
        for file in *.$ext; do
            [ -f "$file" ] || continue
            echo $file
        done
     else
         echo "You have to pass an extension"
     fi;

The statement [ -f "$file" ] || continue is necessary there because of the case I mentioned earlier: if there are no files, the loop will still execute once with the literal expansion of *.$ext.