15

I'm stuck in a quite trivial problem here: how can I make the * symbol in bash mean zero or more, like it does in tools such as sed?

For example, ak* should match any file whose name consists entirely of an a followed by zero or more ks. Its expansion would include a, ak, akk, and akkk, but not akc.

I have already tried unsetopt sh_glob in zsh and set -o noglob in bash; they did not produce the desired behavior.

5
  • Does it have to be glob?
    – heemayl
    Commented Oct 26, 2015 at 23:25
  • I'm not sure actually, I just wanted to point out what I have already tried. I had changed the title to reflect that.
    – Kira
    Commented Oct 26, 2015 at 23:30
  • The word you are looking for is "globbing" or "wildcards" (latter might depends on the context).
    – phk
    Commented Oct 26, 2015 at 23:34
  • Um, the regex ak* in sed would totally match akc (and also just a).
    – thrig
    Commented Oct 26, 2015 at 23:55
  • @thrig, ^ak+$ or ^akk*$ would work.
    – cas
    Commented Oct 27, 2015 at 0:02

4 Answers 4

21

Except for ksh93, none of the usual shells have regular expressions with the same syntax as sed, awk, etc. that can be used for matching files.

Ksh93, bash and zsh have regular expressions with a different syntax that's backward compatible with globs:

  • ? matches any single character (like . in the usual regexp syntax)
  • […] matches a character set in mostly the same way
  • *(FOO) matches any number of occurrences of FOO (like (FOO)* in the usual regexp syntax)
  • similarly +(FOO) matches one or more occurrences, and ?(FOO) matches zero or one occurrence
  • @(FOO|BAR) matches either FOO or BAR
  • Matches apply to the whole string, not a substring; if you want a substring, put * at the beginning and at the end

This syntax needs to be activated with shopt -s extglob in bash and with setopt ksh_glob in zsh. So in bash you'd write

shopt -s extglob
ls a*(k)

See also Why does my regular expression work in X but not in Y?

Ksh93, zsh and bash can do regular expression matching with extended regular expressions (basically the syntax of awk) on strings, with the =~ operator of the [[ … ]] construct. This isn't convenient for listing files though, but if you really want it, it can be done.

shopt -s dotglob  # <<< include dot files, for bash
setopt globdots   # <<< include dot files, for zsh
FIGNORE='@(.|..)' # <<< include dot files, for ksh
for x in *; do
  if [[ $x =~ ^ak*$ ]]; then
    …
  fi
done
4
  • 2
    A simple answer like ls a*(k) would already get you an accepted answer, so thank you for taking your time to make this thorough answer.
    – Kira
    Commented Oct 27, 2015 at 1:59
  • 1
    Now, are you sure that your third bullet is correct? a(k) gives syntax error, therefore I suppose that (foo) alone is not the whole syntax for that part.
    – Kira
    Commented Oct 27, 2015 at 2:05
  • 1
    @Kira I was confused by this, too. Looking at the answer's source, though, it seems that Gilles meant *(foo) rather than (foo). I've proposed an edit to fix up the formatting. Commented Oct 27, 2015 at 5:30
  • ksh93 globs can use regular expressions: echo ~(E:ak*) for ERE. Commented Oct 27, 2015 at 15:48
8

ls ak{k,} will display files beginning with ak followed either by another k or nothing.

$ touch ak akk akc
$ ls -l ak{k,}
-rw-rw-r-- 1 cas cas 0 Oct 27 10:30 ak
-rw-rw-r-- 1 cas cas 0 Oct 27 10:30 akk

globs aren't regexps, but there's more to them than just * and ?.

If you want to use regexps to find matching filenames, you can use the find command:

$ find . -maxdepth 1 -type f -regex './ak+$' 
./ak
./akk

The -maxdepth 1 option limits the search to just the current directory (no subdirectories will be searched)

If you want case-insensitive searches, use -iregex rather than -regex.

There are numerous methods for using the files found by find in other commands. For example:

find . -maxdepth 1 -type f -regex './ak+$' -ls
find . -maxdepth 1 -type f -regex './ak+$' -exec ls -ld {} +
find . -maxdepth 1 -type f -regex './ak+$' -print0 | xargs -0r ls -ld
ls -ld $(find . -maxdepth 1 -type f -regex './ak+$')

The last example is prone to various failure modes, including 1. not coping with white-space etc in filenames, 2. command-line length limits. not recommended.

3
  • I exactly have this in mind but i think OP wants a more generic solution that is not bound to any certain filenames..
    – heemayl
    Commented Oct 26, 2015 at 23:35
  • Your answer is a bit too specific, I want to have a arbitrary number of k's. I'm going to edit the question to reflect that.
    – Kira
    Commented Oct 26, 2015 at 23:35
  • @heemayl, as with regexps, globs need to be crafted for the exact circumstances as required. @kira, see man 7 glob - unlike regex, glob doesn't have a zero-or-more or 1-or-more modifier.
    – cas
    Commented Oct 26, 2015 at 23:40
3

In bash the syntax you can use is: ls a+(k) This depends on the bash shopt shell option extglob being enabled. On Ubuntu 14.04 GNU/Linux, this appears to be enabled by default.

This is how it works:

$ shopt extglob
extglob         on
$ ls
ak  akc  akd  akk  akkk  akkkk
$ ls a+(k)
ak  akk  akkk  akkkk
$ shopt -u extglob
$ shopt extglob
extglob         off
$ ls a+(k)
bash: syntax error near unexpected token `('
$

From the Bash Manual:

+(pattern-list)

Matches one or more occurrences of the given patterns.

a pattern-list is a list of one or more patterns separated by a ‘|’.

See this in the Bash Manual at https://www.gnu.org/software/bash/manual/bash.html#Pattern-Matching.

2

A few options depending on the shell:

$ touch a akk aka
$ ksh -c 'echo a*(k)'
a akk
$ zsh -o kshglob -o nobareglobqual -c 'echo a*(k)'
a akk

(nobareglobqual for that trailing (k) not to be taken as a glob qualifier here)

$ bash -O extglob -c 'echo a*(k)'
a akk

$ zsh -o extendedglob -c 'echo ak#'
a akk

zsh's # is the equivalent of regexp *.

ksh93 can also use several types of regular expressions in its globs:

$ ksh93 -c 'echo ~(E:ak*)' # extended RE
a akk
$ ksh93 -c 'echo ~(P:ak*)' # perl-like RE
a akk
$ ksh93 -c 'echo ~(X:ak*)' # AT&T augmented RE
a akk
$ ksh93 -c 'echo @(~(E)ak*)' # alternative syntax
a akk

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .