Differences between revisions 2 and 18 (spanning 16 versions)
Revision 2 as of 2007-08-15 20:57:07
Size: 4872
Editor: GreyCat
Comment: good enough for a first draft
Revision 18 as of 2011-05-09 14:20:05
Size: 8131
Editor: GreyCat
Comment: options and clean-up
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
"Glob" is the common name for a set of Bash features that match or expand specific types of patterns. Some synonyms for globbing (depending on the context in which it appears) are [http://tiswww.case.edu/php/chet/bash/bashref.html#SEC36 pattern matching], pattern expansion, filename expansion, and so on. A glob may look like {{{*.txt}}} and, when used to match filenames, is sometimes called a "wildcard". "Glob" is the common name for a set of Bash features that match or expand specific types of patterns. Some synonyms for globbing (depending on the context in which it appears) are [[http://tiswww.case.edu/php/chet/bash/bashref.html#SEC36|pattern matching]], pattern expansion, filename expansion, and so on. A glob may look like {{{*.txt}}} and, when used to match filenames, is sometimes called a "wildcard".
Line 3: Line 3:
Traditional shell globs use a very simple syntax, which is less expressive than a regular expression. Most characters in a glob are treated literally, but a {{{*}}} matches 0 or more characters, a {{{?}}} matches precisely one character, and {{{[...]}}} matches any characters in a specified set (see the previous reference for details). All globs are implicitly anchored at both start and end. For example: Traditional shell globs use a very simple syntax, which is less expressive than a RegularExpression. Most characters in a glob are treated literally, but a {{{*}}} matches 0 or more characters, a {{{?}}} matches precisely one character, and {{{[...]}}} matches any single character in a specified set (see the previous reference for details). All globs are implicitly anchored at both start and end. For example:
Line 5: Line 5:
|| `*` || Matches any string, of any length ||
|| `foo*` || Matches any string beginning with `foo` ||
|| `*x*` || Matches any string containing an `x` (beginning, middle or end) ||
|| `*.tar.gz` || Matches any string ending with `.tar.gz` ||
|| `*.[ch]` || Matches any string ending with `.c` or `.h` ||
|| `foo?` || Matches `foot` or `foo$` but not `fools` ||
||`*` ||Matches any string, of any length ||
||`foo*` ||Matches any string beginning with `foo` ||
||`*x*` ||Matches any string containing an `x` (beginning, middle or end) ||
||`*.tar.gz` ||Matches any string ending with `.tar.gz` ||
||`*.[ch]` ||Matches any string ending with `.c` or `.h` ||
||`foo?` ||Matches `foot` or `foo$` but not `fools` ||
Line 14: Line 14:
{{{tar xvf *.tar {{{
tar xvf *.tar
Line 16: Line 17:
# (which is generally not what one wants)}}} # (which is generally not what one wants)
}}}
Line 20: Line 22:
{{{# This is safe even if a filename contains whitespace: {{{
# This is safe even if a filename contains whitespace:
Line 28: Line 31:
done}}} done
}}}
Line 30: Line 34:
In the second example above, the output of {{{ls}}} is filtered, and then the result of the whole pipeline is divided into words, to serve as iterative values for the loop. This word-splitting will occur at internal whitespace within each filename, which makes it useless in the general case. The first example has no such problem, because the filenames produced by the glob do ''not'' undergo any further word-splitting. For more such examples, see BashPitfalls. In the second example above, the output of {{{ls}}} is filtered, and then the result of the whole pipeline is [[WordSplitting|divided into words]], to serve as iterative values for the loop. This word-splitting will occur at internal whitespace within each filename, which makes it useless in the general case. The first example has no such problem, because the filenames produced by the glob do ''not'' undergo any further word-splitting. For more such examples, see BashPitfalls.
Line 32: Line 36:
Globs are also used to match patterns in a few places in bash. The most traditional is in the {{{case}}} command: Globs are also used to match patterns in a few places in Bash. The most traditional is in the [[BashGuide/TestsAndConditionals#Choices|case]] command:
Line 34: Line 38:
{{{case "$input" in {{{
case "$input" in
Line 38: Line 43:
esac}}} esac
}}}
Line 44: Line 50:
{{{if [[ $output = *[Ee]rror* ]]; then ... {{{
if [[ $output = *[Ee]rror* ]]; then ...
Line 47: Line 54:
Finally, globs are used during [:BashFAQ#faq73:parameter expansion] to indicate patterns which may be stripped out, or replaced, during a substitution. Simple examples (there are many more on the previously referenced page): Finally, globs are used during [[BashFAQ/073|parameter expansion]] to indicate patterns which may be stripped out, or replaced, during a substitution. Simple examples (there are many more on the previously referenced page):
Line 49: Line 56:
{{{filename=${path##*/} # strip leading pattern that matches */ (be greedy)
dirname=${path%/*}    # strip trailing pattern matching /* (non-greedy)
{{{
filename=${path##*/} # strip leading pattern that matches */ (be greedy)
dirname=${path%/*} # strip trailing pattern matching /* (non-greedy)
Line 52: Line 60:
IFS=$'\n'; echo "${arr[*]}" # dump an array, one element per line
IFS=$'\n'; echo "${arr[*]/error*/}" # dump an array, removing error* if matched
unset IFS}}}

In addition to the traditional globs (supported by all Bourne-family shells) that we've seen so far, Bash (and Korn Shell) offers ''extended globs'', which have the expressive power of regular expressions. Korn shell enables these by default; in bash, you must run the command

{{{shopt -s extglob
printf '%s\n' "${arr[@]}" # dump an array, one element per line
printf '%s\n' "${arr[@]/error*/}" # dump array, removing error* if matched
Line 61: Line 64:
in your shell (or at the start of your script) to use them. The [http://tiswww.case.edu/php/chet/bash/bashref.html#SEC36 pattern matching reference] describes the syntax, which is reproduced here: (Reference: [[BashGuide/Arrays|Arrays]] [[Quotes]] [[http://bash-hackers.org/wiki/doku.php/commands/builtin/printf|printf]].)

== Options which change globbing behavior ==

=== extglob ===

In addition to the traditional globs (supported by all Bourne-family shells) that we've seen so far, Bash (and Korn Shell) offers ''extended globs'', which have the expressive power of [[RegularExpression|regular expressions]]. Korn shell enables these by default; in Bash, you must run the command

{{{
shopt -s extglob
}}}

in your shell (or at the ''start'' of your script -- see note on parsing below) to use them. The [[http://tiswww.case.edu/php/chet/bash/bashref.html#SEC36|pattern matching reference]] describes the syntax, which is reproduced here:
Line 69: Line 84:
Patterns in a list are separated by `|` characters.
Line 71: Line 88:
{{{# To remove all the files except ones matching *.jpg
rm !(*.jpg)}}}
{{{
# To remove all the files except ones matching *.jpg:
rm !(*.jpg)
# All except *.jpg and *.gif and *.png:
rm !(*.jpg|*.gif|*.png)
}}}
Line 74: Line 95:
{{{# To copy all the MP3 songs except one to your device
cp !(04*).mp3 /mnt}}}
{{{
# To copy all the MP3 songs except one to your device
cp !(04*).mp3 /mnt
}}}
Line 77: Line 100:
{{{# To trim leading and trailng whitespace from a variable
x=${x##+([[:space:]])}; x=${x%%+([[:space:]])} }}}
To use an extglob in a parameter expansion (this can also be done in one BASH statement with [[BashFAQ/067|read]]):

{{{
# To trim leading and trailing whitespace from a variable
x=${x##+([[:space:]])}; x=${x%%+([[:space:]])}
}}}

Extended glob patterns can be nested, too.

{{{
[[ $fruit = @(ba*(na)|a+(p)le) ]] && echo 'Nice fruit'
}}}

Because the `extglob` option changes the way certain characters are parsed, it is necessary to have a newline (not just a semicolon) between the `shopt` command and any subsequent commands that use extended globs. Likewise, you cannot put `shopt -s extglob` inside a function that uses extended globs, because the function as a whole must be parsed when it's defined; the `shopt` command won't take effect until the function is ''called'', at which point it's too late. Therefore, if you use this option in a script, it's best to put it right under the shebang line, or as close as you can get it while still making your boss happy.

=== nullglob ===

If a glob fails to match any filenames, the shell normally leaves it alone. This means the raw glob will be passed on to the command, as in:

{{{
$ ls *.ttx
ls: cannot access *.ttx: No such file or directory
}}}

This allows the command to see the glob you used, and to use it in an error message. If the Bash option '''nullglob''' is set, however, a glob which matches no files will be removed entirely. This is [[BashFAQ/004|useful in scripts]], but somewhat confusing at the command line, since it "breaks" the expectations of many of the standard tools:

{{{
# Good in scripts!
shopt -s nullglob
oggs=(*.ogg)
for ogg in "${oggs[@]}"; do ...

# Bad at the command line!
shopt -s nullglob
ls *.ttx
# Runs "ls" with no arguments, and lists EVERYTHING
}}}

=== dotglob ===

By convention, a filename beginning with a dot is "hidden", and not shown by `ls`. Globbing uses the same convention -- filenames beginning with a dot are not matched by a glob, unless the glob also begins with a dot. Bash has a '''dotglob''' option that lets globs match "dot files":

{{{
shopt -s dotglob nullglob
files=(*)
echo "There are ${#files[@]} files here, including dot files and subdirs"
}}}

It should be noted that when `dotglob` is enabled, `*` will match files like `.bashrc` but ''not'' the `.` or `..` directories. This is orthogonal to the problem of matching "just the dot files" -- a glob of `.*` ''will'' match `.` and `..`, typically causing problems. See next section.

=== GLOBIGNORE ===

The Bash variable (not shopt) `GLOBIGNORE` allows you to specify patterns a glob ''should not'' match. This lets you work around the infamous "I want to match all of my dot files, but not . or .." problem:

{{{
$ echo .*
. .. .bash_history .bash_logout .bashrc .inputrc .vimrc
$ GLOBIGNORE=.:..
$ echo .*
.bash_history .bash_logout .bashrc .inputrc .vimrc
}}}

----
CategoryShell

"Glob" is the common name for a set of Bash features that match or expand specific types of patterns. Some synonyms for globbing (depending on the context in which it appears) are pattern matching, pattern expansion, filename expansion, and so on. A glob may look like *.txt and, when used to match filenames, is sometimes called a "wildcard".

Traditional shell globs use a very simple syntax, which is less expressive than a RegularExpression. Most characters in a glob are treated literally, but a * matches 0 or more characters, a ? matches precisely one character, and [...] matches any single character in a specified set (see the previous reference for details). All globs are implicitly anchored at both start and end. For example:

*

Matches any string, of any length

foo*

Matches any string beginning with foo

*x*

Matches any string containing an x (beginning, middle or end)

*.tar.gz

Matches any string ending with .tar.gz

*.[ch]

Matches any string ending with .c or .h

foo?

Matches foot or foo$ but not fools

Bash expands globs which appear unquoted in commands, by matching filenames relative to the current directory. The expansion of the glob results in 1 or more words (0 or more, if certain options are set), and those words (filenames) are used in the command. For example:

tar xvf *.tar
# Expands to: tar xvf file1.tar file2.tar file42.tar ...
# (which is generally not what one wants)

Even if a file contains internal whitespace, the expansion of a glob that matches that file will still preserve each filename as a single word. For example,

# This is safe even if a filename contains whitespace:
for f in *.tar; do
    tar tvf "$f"
done

# But this one is not:
for f in $(ls | grep '\.tar$'); do
    tar tvf "$f"
done

In the second example above, the output of ls is filtered, and then the result of the whole pipeline is divided into words, to serve as iterative values for the loop. This word-splitting will occur at internal whitespace within each filename, which makes it useless in the general case. The first example has no such problem, because the filenames produced by the glob do not undergo any further word-splitting. For more such examples, see BashPitfalls.

Globs are also used to match patterns in a few places in Bash. The most traditional is in the case command:

case "$input" in
    [Yy]|'') confirm=1;;
    [Nn]*) confirm=0;;
    *) echo "I don't understand.  Please try again.";;
esac

Patterns (which are separated by | characters) are matched against the first word after the case itself. The first pattern which matches, "wins", causing the corresponding commands to be executed.

Bash also allows globs to appear on the right-hand side of a comparison inside a [[ command:

if [[ $output = *[Ee]rror* ]]; then ...

Finally, globs are used during parameter expansion to indicate patterns which may be stripped out, or replaced, during a substitution. Simple examples (there are many more on the previously referenced page):

filename=${path##*/}    # strip leading pattern that matches */ (be greedy)
dirname=${path%/*}      # strip trailing pattern matching /* (non-greedy)

printf '%s\n' "${arr[@]}"          # dump an array, one element per line
printf '%s\n' "${arr[@]/error*/}"  # dump array, removing error* if matched

(Reference: Arrays Quotes printf.)

Options which change globbing behavior

extglob

In addition to the traditional globs (supported by all Bourne-family shells) that we've seen so far, Bash (and Korn Shell) offers extended globs, which have the expressive power of regular expressions. Korn shell enables these by default; in Bash, you must run the command

shopt -s extglob

in your shell (or at the start of your script -- see note on parsing below) to use them. The pattern matching reference describes the syntax, which is reproduced here:

?(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 one of the given patterns.
!(pattern-list)
Matches anything except one of the given patterns.

Patterns in a list are separated by | characters.

Extended globs allow you to solve a number of problems which otherwise require a rather surprising amount of ugly hacking; for example,

# To remove all the files except ones matching *.jpg:
rm !(*.jpg)
# All except *.jpg and *.gif and *.png:
rm !(*.jpg|*.gif|*.png)

# To copy all the MP3 songs except one to your device
cp !(04*).mp3 /mnt

To use an extglob in a parameter expansion (this can also be done in one BASH statement with read):

# To trim leading and trailing whitespace from a variable
x=${x##+([[:space:]])}; x=${x%%+([[:space:]])}

Extended glob patterns can be nested, too.

[[ $fruit = @(ba*(na)|a+(p)le) ]] && echo 'Nice fruit'

Because the extglob option changes the way certain characters are parsed, it is necessary to have a newline (not just a semicolon) between the shopt command and any subsequent commands that use extended globs. Likewise, you cannot put shopt -s extglob inside a function that uses extended globs, because the function as a whole must be parsed when it's defined; the shopt command won't take effect until the function is called, at which point it's too late. Therefore, if you use this option in a script, it's best to put it right under the shebang line, or as close as you can get it while still making your boss happy.

nullglob

If a glob fails to match any filenames, the shell normally leaves it alone. This means the raw glob will be passed on to the command, as in:

$ ls *.ttx
ls: cannot access *.ttx: No such file or directory

This allows the command to see the glob you used, and to use it in an error message. If the Bash option nullglob is set, however, a glob which matches no files will be removed entirely. This is useful in scripts, but somewhat confusing at the command line, since it "breaks" the expectations of many of the standard tools:

# Good in scripts!
shopt -s nullglob
oggs=(*.ogg)
for ogg in "${oggs[@]}"; do ...

# Bad at the command line!
shopt -s nullglob
ls *.ttx
# Runs "ls" with no arguments, and lists EVERYTHING

dotglob

By convention, a filename beginning with a dot is "hidden", and not shown by ls. Globbing uses the same convention -- filenames beginning with a dot are not matched by a glob, unless the glob also begins with a dot. Bash has a dotglob option that lets globs match "dot files":

shopt -s dotglob nullglob
files=(*)
echo "There are ${#files[@]} files here, including dot files and subdirs"

It should be noted that when dotglob is enabled, * will match files like .bashrc but not the . or .. directories. This is orthogonal to the problem of matching "just the dot files" -- a glob of .* will match . and .., typically causing problems. See next section.

GLOBIGNORE

The Bash variable (not shopt) GLOBIGNORE allows you to specify patterns a glob should not match. This lets you work around the infamous "I want to match all of my dot files, but not . or .." problem:

$ echo .*
. .. .bash_history .bash_logout .bashrc .inputrc .vimrc
$ GLOBIGNORE=.:..
$ echo .*
.bash_history .bash_logout .bashrc .inputrc .vimrc


CategoryShell

glob (last edited 2022-10-13 13:52:20 by emanuele6)