Search code examples
bashcentos7quotesdouble-quotestr

Using quotes with tr in Linux


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?


Solution

  • 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 in SET2 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