I came across a strange bug in a Bash script. The script changes the user's input to lowercase. As the following:
# echo AcCCept | tr [:upper:] [:lower:]
I resolved by adding quotes (or double quotes). The strange thing is that the exact same command that failed on system-a, worked fine on system-b.
system-a has Centos7.5.
system-b has Centos7.4.
However, the Bash and tr
versions are the exact same on both.
Output from system-a with the problem:
[root@system-a ~]# echo AcCCept | tr [:upper:] [:lower:]
tr: misaligned [:upper:] and/or [:lower:] construct
[root@system-a ~]#
[root@system-a ~]#
[root@system-a ~]# echo AcCCept | tr [:upper:] [:lower:]
tr: misaligned [:upper:] and/or [:lower:] construct
[root@system-a ~]# echo AcCCept | tr "[:upper:]" "[:lower:]"
acccept
[root@system-a ~]# bash -version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
[root@system-a ~]# tr --version
tr (GNU coreutils) 8.22
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Jim Meyering.
[root@system-a ~]# cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)
[root@system-a ~]#
Output from system-b - works fine:
echo AcCCept | tr [:upper:] [:lower:]
acccept
[root@system-b ~]# echo AcCCept | tr "[:upper:]" "[:lower:]"
acccept
[root@system-b ~]# bash -version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
[root@system-b ~]# tr --version
tr (GNU coreutils) 8.22
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Jim Meyering.
[root@system-b ~]# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
[root@system-b ~]#
What is the reason that the same command failed on system-a and works fine on system-b?
When unquoted, [...]
is subject to pathname expansion. If you, for example, had a file named u
in your directory, [:upper:]
would expand to it:
$ ls # dir is empty
$ echo [:upper:] # [:upper:] doesn't expand to anything and gets printed as is
[:upper:]
$ touch u # filename 'u' created
$ echo [:upper:] # [:upper:] expands to 'u'
u
$ echo '[:upper:]' # [:upper:] is protected by quotes and doesn't get expanded
[:upper:]
Why is that a problem? Quoting from man tr
:
tr [OPTION]... SET1 [SET2]
Only
[:lower:]
and[:upper:]
are guaranteed to expand in ascending order; used inSET2
while translating, they may only be used in pairs to specify case conversion.
This means that if you use [:lower:]
in SET2
, [:upper:]
or [:lower:]
must be used in SET1
. You will get an error otherwise:
$ tr '[:upper:]' '[:lower:]' <<< "TeXt" # SET1: [:upper:] SET2: [:lower:] => OK
text
$ tr 'u' '[:lower:]' <<< "TeXt" # SET2: [:lower:] SET1: not [:upper:] => ERROR
tr: misaligned [:upper:] and/or [:lower:] construct
$ tr '[:upper:]' 'l' <<< "TeXt" # SET1: [:upper:] SET2: l => OK
lelt