Reputation: 651
This happens for the character t, and the value root. Quite perplexing
$ echo [s]
[s]
$ echo [t]
t
$ echo [ t ]
[ t ]
$ echo [root]
t
Upvotes: 44
Views: 2427
Reputation: 439193
To complement devnull's helpful answer:
Using an unquoted string in bash (in most cases) causes it to be interpreted as a pattern (wildcard expression; loosely speaking, a distant, primitive relative of a regular expression).
In your case, the pattern is matched against the names of files and subfolders in the current working folder, as @devnull demonstrates: [root]
means: match any file whose name consists of just 1 character that is either r
or o
(specifying o
twice is redundant) or t
.
This matching of a pattern against filenames is called pathname expansion.
Note that it also applies to unquoted variable references, so the following would yield the same result:
s='[t]' # Assign string _literal_ (since quoted) to variable.
echo $s # Content of $s, since unquoted, is now subject to pathname expansion.
To treat a string (selectively) literally, you must use quoting.
There are three ways of quoting strings in bash
, using your example:
\
- quote individual characters that have special meaning (so-called metacharacters):
echo \[t\]
This ensures that these otherwise special characters are treated as literals.
Enclose the string in single quotes ('...'
):
echo '[t]'
This protects the string from any expansions (interpretation) by the shell.
Caveat: you cannot include a '
itself in a single-quoted string (not even with escaping).
Enclose the string in double quotes ("..."
):
echo "[t]"
This protects the string from some expansions by the shell, while selectively allowing others.
In the case at hand, '[t]'
and "[t]"
behave identically.
However, use of "
allows you to reference variables (parameter expansion), perform command substitutions, and perform calculations (arithmetic expansion)
inside the string, e.g.:
echo "Home, sweet $HOME." # reference to variable $HOME; parameter expansion
echo "Today's date and the current time are: $(date)" # command substitution
echo "Let's put $(( 2 + 2 )) together." # arithmetic expansion
For a list of all expansions performed by bash
, search for section EXPANSION
in man bash
or visit https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html.
Upvotes: 9
Reputation: 123608
[]
denotes a character class, and you have a file named t
in your current directory.
The following should explain it further:
$ ls
$ echo [t]
[t]
$ touch t
$ echo [t]
t
$ echo [root]
t
$ touch r
$ echo [root]
r t
If you want to echo something within []
, escape the [
:
echo \[$var]
Observe the difference now:
$ echo \[root]
[root]
or, as Glenn Jackman points out, quote it:
$ echo '[root]'
[root]
$ echo "[root]"
[root]
Shell Command Language tells that the following characters are special to the shell depending upon the context:
* ? [ # ~ = %
Moreover, following characters must be quoted if they are to represent themselves:
| & ; < > ( ) $ ` \ " ' <space> <tab> <newline>
You could also use printf
to determine which characters in a given input need to be escaped if you are not quoting the arguments. For your example, i.e. [s]
:
$ printf "%q" "[s]"
\[s\]
Another example:
$ printf "%q" "[0-9]|[a-z]|.*?$|1<2>3|(foo)"
\[0-9\]\|\[a-z\]\|.\*\?\$\|1\<2\>3\|\(foo\)
Upvotes: 62
Reputation: 20885
Being not a shell habitué (and not willing to become) I found surprising how filename expansion is designed to behave when no matches are found. I'll report the Bash reference
Bash scans each word for the characters
*
,?
, and[
. If one of these characters appears, then the word is regarded as a pattern, and replaced with an alphabetically sorted list of file names matching the pattern. If no matching file names are found:
- if the shell option
nullglob
is disabled, the word is left unchanged- if the shell option
nullglob
is set the word is removed- If the
failglob
shell option is set, an error message is printed and the command is not executed
The good news is this thing is configurable. The bad one is a script can fail in a number of ways one doesn't expect - at least, I did not, and it took me some time to understand why echo
behaves the way you posted, just to find that it's because of a combination of weird filenames (who ever wants to name a file t
?), hidden configuration (nullglob
disabled, default option but still hidden) and a harmless command.
I said harmless because this is what you get, for example, when the target is ls
(a failure because the file is not found):
raffaele@Aldebaran:~$ mkdir test
raffaele@Aldebaran:~$ cd test
raffaele@Aldebaran:~/test$ touch t
raffaele@Aldebaran:~/test$ ls [t]
t
raffaele@Aldebaran:~/test$ ls [v]
ls: cannot access [v]: No such file or directory
Upvotes: 14
Reputation: 76636
[]
denotes a character class. Simply put, a character class is a way of denoting a set of characters in such a way that one character of the set is matched. [A-Z]
is a very common example — it matches all the alphabets from A
through Z
.
Here are the results of the commands in a new directory:
$ echo [s]
[s]
$ echo [t]
[t]
$ echo [ t ]
[ t ]
$ echo [root]
[root]
As you can see, echo
displayed them as it is. Why? Read the next section.
Every time you type a command on the Terminal and press the ENTER key, bash performs a lot of operations internally before printing out the results to the shell. The simplest example is the expansion of *
:
$ echo *
evil_plans.txt dir1 dir2
Instead of displaying the literal *
as the output, it printed the contents of the directory. Why did this happen? Because *
has a special meaning — it is a wildcard, which can match any character of a filename. It is important to note that the echo
command doesn't actually see the *
at all — only the expanded result.
There are different kinds of expansions:
... and probably more. In this case, file name expansion is the relevant type of expansion.
When you type an echo
command and press ENTER, bash processes the command and splits it into words. Once that is done, it scans the words for the following characters: ?
, *
and [
. All of these are metacharacters and have a special meaning. If bash finds an occurrence of either of these characters, it treats the supplied word as a pattern.
For example, consider the following case:
$ touch foobar foobak fooqux barbar boofar
$ echo foo*
foobar foobak fooqux
As you can see, the *
expanded and listed the matching file names. (In this case, those that begin with foo
.)
Now let's try another example:
$ touch gray.txt grey.txt
$ echo gr?y.txt
gray.txt grey.txt
?
matches a single character. Nothing more. In this case, gray.txt
and grey.txt
both matched the pattern, so both were printed.
Another example:
$ touch hello hullo hallo
$ echo h[aeu]llo
hallo hello hullo
What happened here? As you know from before, [aeu]
is a character class. A character class matches exactly one of the characters in it; it never matches more than one character. In this case, the characters in the character class could match the filenames correctly, so the results were printed out.
If no matching file names are found, the word is left unchanged.
$ echo [s]
[s]
is a character class and it matches just one character — a literal s
. But no matching files were found, so it was returned as it is.
$ echo [t]
[t]
is a character class as well. It matches one single character. There was a file named t
in your directory, meaning there was a match. So it returned the name of the found file name.
$ echo [root]
[root]
matches the following characters: r
, o
, t
. As you probably guessed, o
occurring twice in the character class doesn't make a difference here. A character class can only match a single character. So echo [root]
will try to find filenames that has a matching character. Since a file named t
exists in your directory, it is listed out.
Always use quoting and escaping where needed. They give you general control over parsing, expansion and expansion's results.
Upvotes: 26