user3546411
user3546411

Reputation: 651

Why does bash "echo [t]" result in "t" not "[t]"

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

Answers (4)

mklement0
mklement0

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

devnull
devnull

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

Raffaele
Raffaele

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

Amal Murali
Amal Murali

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.

Expansion

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:

  • Brace expansion
  • Tilde expansion
  • Parameter expansion
  • Command expansion
  • Arithmetic expansion
  • Process substitution
  • File name expansion

... and probably more. In this case, file name expansion is the relevant type of expansion.

File name 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.

Explanation for your specific case

$ 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.

How to avoid such quirks?

Always use quoting and escaping where needed. They give you general control over parsing, expansion and expansion's results.

Upvotes: 26

Related Questions