0

How to iterate over files in the current directory and exclude some files having specific name patters? The solution must be POSIX compatible.

Assume the files to exclude follow the patterns: test[0-9].txt and work-.* (using regex).

The code I have so far:

for file in * 
do 
    if test "$file" != "test[0-9].txt" -o "$file" != "work-.*"
    then 
        echo "$file"
    fi 
done 

At the moment the output is all files in the working directory. I'm pretty sure pattern matching in test is incorrect, but how can I fix it?

0

2 Answers 2

2
if test "$file" != "test[0-9].txt" -o "$file" != "work-.*"

The test utility doesn't do pattern matching. For that, you need the Ksh/Bash/Zsh [[ ]] construct (and at least in Bash, the pattern needs to be not quoted). So:

for file in *; do 
    if [[ "$file" != test[0-9].txt && "$file" != work-.* ]]; then 
        echo "$file"
    fi 
done 

would print hello.txt, if it exists, but not test1.txt or work-.foo.

Note that you need and instead of or there, as (x ≠ a or x ≠ b) would match any x.

See:

In strict POSIX sh, you'd need to use case to do the pattern match:

for file in *; do 
    case $file in
        test[0-9].txt|work-.*) false;;
        *) echo "$file";;
    esac
done 

Note that if you want to match multiple digits after test, as in test123.txt, then you'll need ksh style extended globs and test+([0-9]).txt (and shopt -s extglob in Bash to have them work in all contexts). Though like Kusalananda notes, if you go that way, you may as well do:

for file in !(test[0-9].txt|work-.*); do
    echo "$file"
done
1
  • Also, using ksh globbing: printf '%s\n' !(test[0-9].txt|work-.*) (untested)
    – Kusalananda
    Apr 1, 2021 at 11:40
1

You can make a string with all possible combinations:

all=:
for thing in *test[0-9].txt work-.*; do
    all=$all$thing:
done
if [[ "$all" == *:"$file":* ]]; then
    # it is in the list
6
  • That for goes in or before the other for, by the way, and doesn't replace it.
    – kettle
    Apr 1, 2021 at 5:48
  • 1
    This is not POSIX code, as requested by the user in the question. It also fails on filenames that contains certain combinations of valid filename characters.
    – Kusalananda
    Apr 1, 2021 at 11:38
  • "*:$file:*" treats * literally, not as a pattern matching character. It should be *:$file:* (with no quotes). Also (as Kusalananda pointed out), try with touch a:work-.f work-.:a work-.f (a:work-.f is also matched).
    – fra-san
    Apr 1, 2021 at 11:41
  • @fra-san, or rather, *:"$file":*
    – ilkkachu
    Apr 1, 2021 at 11:47
  • @ilkkachu Yes, good point. (Even though word splitting and filename expansion are not performed in [[ ]], the expansion of the unquoted $file will still be used as a pattern).
    – fra-san
    Apr 1, 2021 at 12:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .