2

I have a dataset with files named "file000.txt" to "file999.txt".

Im aware that I can list all files from 0 to 500 using wildcards with

ls file{0..500}

And that I can find all files containing an odd number with

ls file*[1,3,5,7,9].txt

Im new to Unix, so I am not sure how to combine both these arguments to list all the files containing an odd number, in the range of 0-500, preferably in one line. Im sure that has to be a quick solution, but I wasnt able to figure it out.

  • 1
    At least in recent versions of the bash shell, brace expansion accepts an optional increment {x..y[..incr]} – steeldriver Apr 27 '22 at 11:33
  • @steeldriver, though brace expansion could generate strings that aren't existing filenames, or globs that don't match anything. Either would give at least error messages by default. – ilkkachu Apr 27 '22 at 13:01

3 Answers3

4

With zsh:

set -o extendedglob
print -rC1 -- file(<0-500>~^*[13579]).txt

Note that {0..50} (from zsh) is not a glob operator, it's brace expansion, that expands regardless of whether corresponding files exist or not.

<0-500> (zsh specific, not copied by other shells yet) is a glob operator, it matches on sequences of ASCII decimal digits that make up a number between 0 and 500.

~ is the except (and not) operator and ^ is not. A~B is A and not B, A~^B is A and not not B, so A and B.

In bash, you could do something similar with:

shopt -s failglob extglob
printf '%s\n' file*(0)[01234][0123456789][13579].txt

*(0) (a extended glob operator from ksh) being any number of zeros, and then we expect a digit from 0 to 4, one from 0 to 9 and one in the 13579 set.

Now, for brace expansion to expand to 1, 3, ... 499 regardless of whether corresponding files exist, in both zsh and bash (and ksh93¹ and yash -o braceexpand):

printf '%s\n' file{1..499..2}.txt

Other notes:

  • [1,3,5,7,9] is the same as [13579,], it matches on either of the characters in that set. You want [13579] instead.
  • in bash (not zsh), [0-9] generally matches on hundreds of different digit-like characters. Use [0123456789] if you only want to match on those 10 ASCII decimal characters.
  • Beware that if you don't pass the -d option to ls and any of the filexxx.txt files you pass to it (as the result of the shell's brace expansion or filename generation aka globbing) is of type directory, ls will list the contents of the directory instead. You almost always want to use -d when passing glob expansions to ls. Though, here, ls will just end up sorting and printing the list given by the shell (after having verified that the files exist) so is mostly superflous. You might as well use the print or printf utilities to just print that list.

¹ {a,b} brace expansion is from csh in the late 70s, zsh extended it with {4..10} (and {a-zXYZ} with braceccl) in the early 90s), ksh93 extended it to {1..400..2%04d} in the mid-2000s, the {1..400.2} part made it back to other shells, the %04d one is still specific to ksh93 AFAIK, though zsh had {0001..0400} for that from the start and generally has separate operators for padding.

  • I guess one could do something like shopt -s nullglob; shopt -u failglob; printf '%s\n' [f]ile{1..499..2}.txt if they wanted to use brace expansion but also remove the strings that don't match existing files... – ilkkachu Apr 27 '22 at 13:05
  • @ilkkachu, printf would still print an empty line if there was no match, you'd rather use print -rC1 file{1..499..2}.txt(N) in zsh which prints nothing if there's no argument. But that would be expanding 250 globs so be a lot less efficient than the one-glob based approach. – Stéphane Chazelas Apr 27 '22 at 13:14
  • Yep to both. Though braces do offer the numeric step, which might come up useful if one ever wants e.g. every third file (which isn't that easily done with a glob). – ilkkachu Apr 27 '22 at 13:25
2

Use ls file[0-4][0-9][13579].txt to list file ending with an odd digit, use ls file[0-4][13579][0-9].txt to list file with an odd digit in middle.

final ls is

 ls file[0-4][0-9][13579].txt file[0-4][13579][0-9].txt file[13][0-9][0-9].txt

note that file*[1,3,5,7,9].txt will match file100001.txt, which is not what you want. Also, you don't the commas as separators within brackets: [1,3,5,7,9] is the same as [13579,] and matches the comma too.

Archemar
  • 31,554
1

Finding the odd numbers is easy, as the other answers show. Mostly because it suffices to look at the last digit

For a more general case, you'd need to actually treat the numbers as numbers. It's hard to do that directly, but you could e.g. loop over the bigger set, and pick the matching ones in an array. The shell (and esp. Bash) isn't too quick in doing this sort of stuff, but for mere hundreds or low thousands of files it might do.

Here for files with numbers divisible by seven (In Bash, this would match numbers with leading zeroes):

shopt -s extglob
a=()
for f in file+([0-9]).txt; do 
    n=${f##file*(0)}      # remove leading 'file' and any zeroes
    n=${n%.txt}           # remove tailing '.txt'
    if (( $n % 7 == 0 )); then
        a+=("$f")
    fi
done
printf "%s\n" "${a[@]}"    # or whatever you do with the filenames

or, you could just loop over the numbers first (Bash and others, this would not see numbers with leading zeroes):

a=()
for ((i = 0; i <= 500; i += 7)); do
    f="file${i}.txt"
    if [[ -e "$f" ]]; then
        a+=("$f")
    fi
done
printf "%s\n" "${a[@]}"

Or, with a brace expansion generating a pile of strings that are artificially made into globs so the shell can be told to remove the non-matching ones. (The [f] here is a pattern that should only match the letter f, maybe) This is a bit brute-forcey and might not be that efficient, but it's a bit more compact in that it doesn't use an explicit loop. (Bash, also wouldn't see numbers with leading zeroes.)

shopt -s nullglob   # remove non-matching globs
shopt -u failglob
a=([f]ile{0..499..7}.txt)
printf "%s\n" "${a[@]}"
ilkkachu
  • 138,973