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 called o since we can see that tr deleted the letter o.
  • 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. Since tr 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.