This is kind of a weird question, seeing as zsh is the only shell with this feature. It's called glob qualifiers. The manual is, as usual, rather terse and devoid of examples. The Zsh-lovers page has a few examples. Googling zsh "glob qualifiers"
turns up a few blog posts and tutorials. You can also search for "glob qualifier"
on this site.
The basics: glob qualifiers are in parentheses at the end of the glob. The most useful ones are the punctuation signs to select only certain file types.
echo *(/) # directories
echo *(.) # regular files
echo *(@) # symbolic links
echo *(-/) # directories and symbolic links to directories
There are other qualifiers to filter on metadata such as size, date and ownership.
# files owned by the user running zsh, over 1MB, last modified more than 7 days ago
echo *(ULm+1m+7)
Glob qualifiers can also control the order of matches, and restrict the number of matches.
echo *(Om[1,10]) # The 10 oldest files
You can set up arbitrary filters by calling a function, with the +
qualifier (you can even put the code inline with the e
qualifier, if you don't mind the tricky quoting).
Note that unfortunately all of this only works on globs. If you want to build a list of file names this way, you need to filter when you're globbing. If you want to filter a list that you've already built, there's a completely different syntax, parameter expansion flags, which can only perform simple text filtering ("${(@)ARRAY:#PATTERN}"
).
You can disable the PATTERN(QUALIFIERS)
syntax by unsetting the bare_glob_qual
option:
setopt no_bare_glob_qual
If the option extended_glob
is set (and you should set it, the only reason not to set it is for backward compatibility with rare scripts that use unusual syntax), then there is another syntax for glob qualifiers: PATTERN(#qQUALIFIERS)
. So you can still use glob qualifiers, which are one of zsh's killer features, but you'll have to type a bit more.
Zsh lets you disable wildcard expansion (globbing) altogether, and this looks like a better choice for you. If a command is prefixed by noglob
, then no globbing is performed on its arguments. For example, to be able to type URLs containing ?
as arguments to wget
, I have alias wget='noglob wget'
. If you set alias ag='noglob ag'
, you can type ag mymethod(param)
.
If ag
takes both a search pattern and file names as arguments, then disabling globbing is not good. If you're able to parse the arguments of ag
, then you can perform wildcard expansion on them. I don't know the syntax of ag
, so I'll give an example where I assume that ag
only takes options that don't take arguments, and that its first non-option argument is a pattern and the rest are files.
ag () {
local i=1
while [[ ${(P)i} = -* ]]; do ((++i)); done
if ((i < $#)); then
set -- "${(@)@[1,$i]}" $~@[$((i+1)),$#]
fi
}
alias ag='noglob ag'
Best Answer
That would be the first time I see anybody complaining about that (we more often see people complaining about it not doing word splitting upon parameter expansion).
Most people expect
to output the content of the
$file
variable and are annoyed when shells likebash
don't (a behaviour inherited from the Bourne shell, unfortunately not fixed by ksh and specified by POSIX for thesh
interpreter), and that's causing a lot of bugs and security vulnerabilities and that's why you need to quote all the variables in those shells.See for instance: Security implications of forgetting to quote a variable in bash/POSIX shells
I see that you're expecting that too as you're writing
echo $0
and notecho "$0"
.zsh
has fixed that. It does neither globbing nor word splitting by default upon parameter expansion. You need to request those explicitly:echo $=file
: perform word splittingecho $~file
: perform globbingecho $=~file
: perform bothOr you could turn on the
globsubst
andshwordsplit
options to get the same behaviour as in Bourne-like shells (those two options are enabled whenzsh
is invoked assh
forsh
compatibility), but I would not recommend that unless you needzsh
to interpret code written for another shell (and even in that case, it would make more sense to interpret that code insh
emulation in a local context withemulate -L sh
).Here naming your variable
file
inis misleading if you intend it to be expanded upon expansion¹
would make more sense. If you want a variable holding the name of all the non-hidden files in the current directory, you'd do:
or:
for that assignment not to fail if there's no non-hidden file in the current directory.
That is, use an array variable assignment. That (
file=(*)
) would work the same as inbash
orksh93
,mksh
oryash
, except thatzsh
doesn't have that other misfeature of the Bourne shell whereby the pattern is left unexpanded when there's no match.¹Note that
*
is a perfectly valid name for a file on Unix-like system. I take some comfort in thatrm -f -- $file
removes the file whose name is stored in$file
even if that file is called*
.