Strange behaviour of tr using ranges
you have a file named o
in current directory
foo> ls
foo> echo "abcdefghijklmnopqrstuvwxyz1234567890"|tr -d [a-z]
1234567890
foo> touch o
foo> echo "abcdefghijklmnopqrstuvwxyz1234567890"|tr -d [a-z]
abcdefghijklmnpqrstuvwxyz1234567890
shell will expand [a-z]
string if a match is found.
This is called pathname expansion, according to man bash
Pathname Expansion
After word splitting, unless the -f option has been set, bash scans each word for the characters *, ?, and [. ... (...)
bash will perform expansion.
[...] Matches any one of the enclosed characters.
What is happening
The shell (bash) sees the argument [a-z]
. That's a wildcard pattern (a glob), which matches any lowercase letter¹. Therefore the shell looks for a file name that matches this pattern. There are three cases:
- No file in the current directory has a name that is a single lowercase letter. Then the shell leaves the wildcard pattern unchanged, and
tr
sees the arguments-d
and[a-z]
. This is what happens on most of your machines. - A single file in the current directory has a name that is a single lowercase letter. Then the shell expands the pattern to this file name, and
tr
sees the arguments-d
and the file name. This happens on the server, and the matching file is calledo
since we can see thattr
deleted the lettero
. - Two or more files in the current directory have a name that is a single lowercase letter. Then the shell expands the pattern to the list of matching file names, and
tr
sees three or more arguments:-d
and the file names. Sincetr
expects a single argument after-d
, it will complain.
What you should have done
If there are special characters in the argument of a command, you must escape them. Put the argument in single quotes '…'
(this is the simplest way, there are others). Inside single quotes, all characters stand for themselves except the single quote itself. If there is a single quote inside the argument, replace it by '\''
.
tr -d '[a-z]'
However note that this is still probably not what you meant! This tells tr
to delete lowercase letters and square brackets. It's equivalent to tr -d ']a-z['
, tr '[]a-z'
, etc. To delete lowercase letters, use
tr -d a-z
The argument to tr
is a character set. You put brackets around a character set in a regular expression or wildcard pattern to indicate that it's a character set. But tr
works on a single character at a time. Its command line arguments are what you'd put inside the brackets.
You do need brackets to indicate character classes. In a regular expression, you use brackets inside brackets to indicate a character class, e.g. [[:lower:]]*
matches any number of lowercase letters, [[:lower:]_]*
matches any number of lowercase letters and underscores. In the argument of tr
, you need the set without its surrounding brackets, so tr -d '[:lower:]'
deletes lowercase letters, tr -d '[:lower:]_'
deletes lowercase letters and underscores, etc.
¹ In some locales it may match other characters.