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?

J Szum
  • 161

2 Answers2

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
ilkkachu
  • 138,973
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
kettle
  • 226
  • That for goes in or before the other for, by the way, and doesn't replace it. – kettle Apr 01 '21 at 05: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 01 '21 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 01 '21 at 11:41
  • @fra-san, or rather, *:"$file":* – ilkkachu Apr 01 '21 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 01 '21 at 12:13
  • Oh yeah, thanks. – kettle Apr 02 '21 at 00:10