Bash – Guide to Extended Globbing

bashwildcards

I'm unable to show JUST ONE file from the output of ls using bash extended globbing.

From info bash

  If the `extglob' shell option is enabled using the `shopt' builtin,
several extended pattern matching operators are recognized.  In the
following description, a PATTERN-LIST is a list of one or more patterns
separated by a `|'.  Composite patterns may be formed using one or more
of the following sub-patterns:

`?(PATTERN-LIST)'
     Matches zero or one occurrence of the given patterns.

`*(PATTERN-LIST)'
     Matches zero or more occurrences of the given patterns.

`+(PATTERN-LIST)'
     Matches one or more occurrences of the given patterns.

`@(PATTERN-LIST)'
     Matches exactly one of the given patterns.

`!(PATTERN-LIST)'
     Matches anything except one of the given patterns.

.

shops -s extglob

ls -l /boot/@(vmlinuz*)
-rw-r--r--  1 root root 1829516 Apr 21  2009 /boot/vmlinuz-2.6.9-89.EL
-rw-r--r--  1 root root 1700492 Apr 21  2009 /boot/vmlinuz-2.6.9-89.ELsmp

ls -l /boot/?(vmlinuz*)
-rw-r--r--  1 root root 1829516 Apr 21  2009 /boot/vmlinuz-2.6.9-89.EL
-rw-r--r--  1 root root 1700492 Apr 21  2009 /boot/vmlinuz-2.6.9-89.ELsmp

How do I show only ONE file?

Best Answer

Bash has no feature to expand just one match out of many.

The pattern @(foo) matches just one occurrence of the pattern foo. That is, it matches foo, but not foofoo. This syntactic form is useful to build or patterns like @(foo|bar), which matches either foo or bar. It can be used as part of longer patterns like @(foo|bar)-*.txt, which matches foo-hello.txt, foo-42.txt, bar-42.txt, etc.

If you want to use one match among many, you can put the matches in an array, and then use an element of the array.

kernels=(vmlinuz*)
ls -l "${kernels[0]}"

Matches are always sorted in lexicographic order, so this will print the first match in lexicographic order.

Note that if the pattern doesn't match any file, you'll get an array containing a single element which is the unchanged pattern:

$ a=(doesnotmatchanything*)
$ ls -l "${a[0]}"
ls: cannot access doesnotmatchanything*: No such file or directory

Set the nullglob option to get an empty array instead.

shopt -s nullglob
kernels=(vmlinuz*)
if ((${#kernels[@]} == 0)); then
  echo "No kernels here"
else
  echo "One of the ${#kernels[@]} kernels is ${kernels[0]}"
fi

Zsh has convenient features here. The glob qualifier [NUM] causes the pattern to expand to only the NUMth match; the variant [NUM1,NUM2] expands to the NUM1th through NUM2th matches (starting at 1).

% ls -l vmlinuz*([1])
lrwxrwxrwx 1 root root 26 Nov 15 21:12 vmlinuz -> vmlinuz-3.16-0.bpo.3-amd64
% ls -l nosuchfilehere*([1])
zsh: no matches found: nosuchfilehere*([1])

The glob qualifier N causes the pattern to expand to an empty list if no file is matched.

kernels=(vmlinuz*(N))
if ((#kernels)); then
  ls -l $kernels
else
  echo "No kernels here"
fi

The glob qualifier om sorts matches by increasing age instead of by name (m is for modification time); Om sorts by decreasing age . So vmlinuz*(om[1]) expands to the most recent kernel file.