This answer focuses on zsh. Most of it can't be done easily in bash.
Many common cases can be done with wildcards. In particular:
- Everything with a fixed prefix:
foo-*
- Everything with a fixed prefix followed by one more letter within a range:
foo[b-m]*
(includes foobar
, food
, foomz1
, but not fooar
or foon
)
- Numbers in a range:
IMG-<7-42>.*
(includes IMG-8.PNG
and IMG-0042.JPG
but not IMG-77.JPG
)
With glob qualifiers, there's an easy way to identify a range, but it requires counting: foo*([1,3])
matches the first 3 files listed by foo*
, whatever they are (or all files if there are fewer than 3). This takes place after whatever sorting is done, for example foo*(om[1,3])
matches the three most recently modified files whose name starts with foo
.
You can make zsh figure out the numbers for you. Do it in two steps: first put all the matches in an array, then find the endpoints using the subscript flags i
and I
(also e
if you want to prevent any wildcard matching): $a[$a[(i)foo],$a[(I)bar]]
is the part of the array $a
from the element foo
to the element bar
inclusive, and empty if either foo
or bar
is not present.
a=(*.txt(oL))
# List the files that appear between small.txt and large.txt in a listing by size.
# Note that files that have the same size as one of the bounds may or may not be included.
echo $a[$a[(I)small.txt],$a[(I)large.txt]]
So here's a function that implements exactly the requirements of the question (except the exact syntax, which can't be done):
# Usage: select_range FROM TO WILDCARD_PATTERN
# Sets the array $s to the files matching PATTERN from FROM to TO inclusive.
function select_range {
if (($# < 2)); then
echo >&2 "select_range: missing range arguments"
return 120
fi
local from=$1 to=$2
shift 2
from=$@[(ie)$from]
if ((from == 0)); then
echo >&2 "select_range: not matched: $from"
fi
to=$@[(Ie)$to]
if ((to == 0)); then
echo >&2 "select_range: not matched: $from"
fi
s=($@[$from,$to])
}
Usage: select_range aoeitoae.txt oaie.txt * && rm $s
The e
glob qualifier lets you write arbitrary code to filter results, but it already starts getting a little unwieldy. Quoting can be tricky in complex cases; to keep things simple, use '
as the delimiter (which needs to be quoted with a backslash) and put the filter code in single quotes, meaning the pattern looks like this: foo-*(e\''code goes here'\')
. (If the quoting gets too complicated, write a function and use the +
qualifier.) To filter files that come after aoeitoae.txt
and before oaie.txt
in lexicographic order: *(e\''! [[ $REPLY < aoeitoae.txt || $REPLY > oaie.txt ]]'\')
.
Note that the comparisons done in the filter don't necessarily use the same order as the wildcard expansion. For example, foo-*(n)
lists foo-9
before foo-10
thanks to the n
qualifier, but [[ foo-9 > foo-10 ]]
in a string comparison, and there's no conditional operator similar to >
that compares integer substrings numerically. If you want to do a string comparison with integer parts sorted numerically, you can use the n
parameter expansion flag for array sorting and check that it keeps the matched name in the middle: *(ne\''a=(b11r $REPLY f10o); [[ $a[2] == "${${(@n)a}[2]}" ]]'\'))
includes b101r
, b11s
, d1
, f02o
, …, but not b9r
, f011
, …
If you're matching files by date, you can use the -nt
conditional (note that a file is not newer than itself): *(ome\''! [[ $REPLY -ot from || $REPLY -nt to ]]'\')
only includes files modified between the modification time of from
and the modification time of to
, inclusive.
ranger
ormc
might make sense :) – Marcus Müller Feb 15 '23 at 20:20ls -1 | sed -n -e '/first_file/,/last_file/p'
? – U. Windl Feb 16 '23 at 00:37sed
won't work, but I'll propose a nice one usinged
instead. – U. Windl Feb 17 '23 at 09:32rm [a-o]*.txt
would remove the 3 files you want. Perhaps you could craft a better example, like one where there's anoab....txt
you don't want to remove, and you don't want to userm -i
to manually say yes to all but the last; easy to get that wrong. – Peter Cordes Feb 18 '23 at 01:35