6

Say that in a given directory I got

tzury@x200:~/Desktop/sandbox$ ls -l
total 20
drwxr-xr-x 2 tzury tzury 4096 2011-03-09 10:19 N00.P000
drwxr-xr-x 2 tzury tzury 4096 2011-03-09 10:19 N00.P001
drwxr-xr-x 2 tzury tzury 4096 2011-03-09 10:19 N00.P002
drwxr-xr-x 2 tzury tzury 4096 2011-03-09 10:19 N00.P003
drwxr-xr-x 2 tzury tzury 4096 2011-03-09 10:19 N00.P004
drwxr-xr-x 2 tzury tzury 4096 2011-03-09 10:19 N01.P000
drwxr-xr-x 2 tzury tzury 4096 2011-03-09 10:19 N01.P001
drwxr-xr-x 2 tzury tzury 4096 2011-03-09 10:19 N01.P002

I seek for a bash way to grab the list of files which their name is either grater or smaller than a given parameter, for instance:

$ my_finder lt N00.P003

shall return N00.P000, N00.P001 and N00.P002

$ my_finder gt N00.P003

shall return N00.P004, N01.P000, N01.P001 and N01.P002

I was thinking of iterating over for name in $(ls) and while $name != $2 but believe there are more elegant way of doing so

3 Answers 3

3

Never ever iterate over ls output!

Here's my suggestion:

for fn in *; do test "$fn" -$1 "$2" && echo "$fn"; done

Edit:

Sorry. The above works only if $fn and $2 are numeric. You'll have to replace -$1 with $op, and prepend a selector in front of the loop. op="<" or op=">" depending on $1 is lt or gt, respectively.

2
  • 1
    many thanks. in regards ot the advise of "never ever iterate over ls (or find)" I would love if you can elaborate about this ab it more, and what if in a case where I iterate over a directory which is content is totally under my control, is this advise still applies? Mar 9, 2011 at 10:34
  • 2
    The problem with ls is that it will happily list files whose names contain space or other whitespace, and thus such files' names will be treated as separate words. Unless you take care to actually wrap each file. Now, word splitting happens before globbing. So, the '*' will not experience word splitting when it is fed to the for loop.
    – pepoluan
    Mar 9, 2011 at 10:54
2

Unfortunately for this technique, /usr/bin/test doesn't support STRING > STRING, however the shell builtin test does so we have to invoke the shell in order to be able to use find -exec and avoid a loop:

find $PWD -type f -exec sh -c 'test "{}" "<" "$PWD/N00.P004"' \; -print

The question remains whether spawning a shell repeatedly is more efficient than running a loop. However, the chief advantage to using this technique is that you can do recursive processing without a pipe.

You can create a function that uses this technique and allows you to use gt and lt instead of having to pass quoted < or >:

my_finder () { 
    local op=$1
    case "$op" in
        "gt") op='>';;
        "lt") op='<';;
           *) echo "Invalid operator"; return 1;;
    esac
    find $PWD -type f -exec sh -c 'test "{}" "$op" "$PWD/$2"' \; -print
}

Usage:

$ my_finder gt N00.P003
/home/tzury/Desktop/sandbox/N00.P004
/home/tzury/Desktop/sandbox/N01.P000
/home/tzury/Desktop/sandbox/N01.P001
/home/tzury/Desktop/sandbox/N01.P002
2
  • what do you mean does not support S1 > S2? @pepoluan's answer works for me in both cases, gt and lt Mar 10, 2011 at 3:44
  • @TzuryBarYochay: That answer is using the shell builtin test, I was referring to /usr/bin/test as the first sentence in my answer says. They're not the same thing, though they're very similar. What I failed to mention in my answer is that, if /usr/bin/test did support < and > then my find command could be written: find $PWD -type f -exec test "{}" "<" "$PWD/N00.P004" \; -print. I will add a clarification. Mar 10, 2011 at 3:54
1
for num in {001..003} ;do ls N00.P"$num"; done

Replace 003 with limit you want to put.

1
  • it is missing from my example though, it can happen that parameter might be N01.P001 which means in that case, N00.P"$num" pattern won't work Mar 9, 2011 at 9:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.