0

What is the difference between using code $variable and ${variable}2 ? Example:

file ='GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT'
prefix ='GLOBAL_AR_COLLECTOR_COMMENTS'

In my shell script code written like below,

if echo $file |grep -q $prefix*.TXT

if echo $file | grep -q ${prefix}2*.TXT

i would like to know, what is the difference between both the if conditions?. What is the use of {prefix} ? , and why we are using 2 with ${prefix}2?

1

5 Answers 5

6

Braces

Quoting the subsection 2.6.2 Parameter Expansion of the POSIX Shell & Utilities:

The parameter name or symbol can be enclosed in braces, which are optional except for positional parameters with more than one digit or when parameter is a name and is followed by a character that could be interpreted as part of the name.

What this means is:

  1. To use the positional parameters ($0, $1, $2, ...) beyond $9, you need to use braces: ${10}.
  2. When using parameters with characters which could be interpreted as part of the name, like in your example: $prefix2, braces are used to explicitly specify that the character (2) should not be considered part of the name. So, $prefix2 expands into whatever is the contents of prefix2, while ${prefix}2 expands into whatever is the contents of prefix parameter with 2 added. Example:
    $ prefix=Mr.
    $ prefix2=Mrs.
    $ printf "%s Smith\n" $prefix2
    Mrs. Smith
    $ printf "%s Smith\n" ${prefix}2
    Mr.2 Smith
    
    Another example in Bash, where braces do the opposite from the previous example:
    $ fruits=(apples bananas oranges)
    $ printf "%s\n" $fruits[1]
    apples[1]
    $ printf "%s\n" ${fruits[1]}
    bananas
    
    Here, [ is normally not considered to be a part of the name, so adding braces forces it to be considered a part of the name. This is specific to Bash and other shells which support arrays, like mksh, etc (zsh doesn't need braces here); other POSIX shells which don't support arrays, like dash, give an error Bad substitution when expanding ${fruits[1]}.

Characters which constitute Names are defined in the 3.216 Name subsection in the POSIX Base Definitions chapter:

In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set. The first character of a name is not a digit.

So, as far as POSIX is concerned, prefix2 is a name, while fruits[1] is not a name (in Bash, zsh, and so on, it is, but in Bash it needs braces; in zsh, it does not).

grep and globbing

If the concern is about the grep invocation

grep -q $prefix*.TXT

first, note that Unix-like filesystems are case-sensitive. This means that FILE.TXT, FILE.txt, file.TXT, file.txt and FiLe.TxT are five separate filenames.

Next, due to Pathname Expansion ("globbing"), the command

grep -q file*

entirely depends on whether there are, and how many, files whose names begin with file:

  1. If there are no files with names starting with file, the literal string file* will be passed by the shell to grep, which by default searches its stdin. So a command like
    echo $something | grep -q file*
    
    will search the contents of the parameter something for text beginning with fil, followed by zero or more e:
    $ something=Hello
    $ echo $something | grep file*    # grep 'file*'
    $ echo $?
    1
    $ something=fil
    $ echo $something | grep file*
    fil
    $ something=fileeee
    $ echo $something | grep file*
    fileeee
    $
    
  2. If there is one file with name starting with file, its name will be passed by the shell to grep as a search pattern, and grep will search its stdin for that pattern. This can have unexpected results if the filename contains characters special to regular expressions, like ., ^ and so on.
    $ touch file.txt
    $ something=fileeee
    $ echo $something | grep file*    # grep 'file.txt'
    $ echo $?
    1
    $ something=file.txt
    $ echo $something | grep file*
    file.txt
    $ something=fileatxt
    $ echo $something | grep file*
    fileatxt
    $
    
  3. If there are two or more files with names starting with file, the name of the first file (in glob(3p) order) will be passed to grep as the search pattern, with all the implications of point 2. above, and grep will search the remaining files with names starting with file, ignoring whatever is passed to its stdin:
    $ touch file.txt
    $ echo file.txt >filea.txt
    $ echo fileatxt >fileb.txt
    $ something=Hello
    $ echo $something | grep file*    # grep 'file.txt' 'filea.txt' 'fileb.txt'
    filea.txt:file.txt
    fileb.txt:fileatxt
    $
    

Conclusion

Combining the previous two sections, the answer depends on the number and names of the files in the current directory, as well as the presence of parameter named prefix2. We can use set -x to show what is passed by the shell to utilities.

When there are no files ending with .TXT:

$ file='GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT'
$ prefix='GLOBAL_AR_COLLECTOR_COMMENTS'

(Note: there is no space before =! With space present, the shell word splitting will interpret file as a command name.)

$ set -x; echo $file | grep $prefix2*.TXT; echo $?; set +x
+ echo GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT
+ grep '*.TXT'
+ echo 1
1
+ set +x
$ set -x; echo $file | grep ${prefix}2*.TXT; echo $?; set +x
+ echo GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT
+ grep 'GLOBAL_AR_COLLECTOR_COMMENTS2*.TXT'
+ echo 1
1
+ set +x
$

Since there is no prefix2 parameter, $prefix2 expands to an empty string. Now, if we define prefix2:

$ prefix2=foo
$ set -x; echo $file | grep $prefix2*.TXT; echo $?; set +x
+ echo GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT
+ grep 'foo*.TXT'
+ echo 1
1
+ set +x
$ set -x; echo $file | grep ${prefix}2*.TXT; echo $?; set +x
+ echo GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT
+ grep 'GLOBAL_AR_COLLECTOR_COMMENTS2*.TXT'
+ echo 1
1
+ set +x
$

Here, GLOBAL_AR_COLLECTOR_COMMENTS2*.TXT is interpreted as a regular expression and not a glob, so 2* means "zero or more of the character 2", not "character 2 followed by any combination of characters". And now, with file with the name from the parameter file present:

$ touch $file
$ set -x; echo $file | grep $prefix2*.TXT; echo $?; set +x
+ echo GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT
+ grep 'foo*.TXT'
+ echo 1
1
+ set +x
$ set -x; echo $file | grep ${prefix}2*.TXT; set +x
+ echo GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT
+ grep GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT
GLOBAL_AR_COLLECTOR_COMMENTS20240614ALYSSAB.TXT
+ set +x
$

Due to this answer already reaching a considerable length, I've left out the case with several files matching the ${prefix}2*.TXT glob, but using this method and the information in the section grep and globbing above, you can see how that case would differ from the other cases.

4

It is possible to include digits in your variable names. As such it is possible for a variable name to end with a digit. Therefore, if you want to refer to a variable's value, followed immediately by the digit 2, you have to use the ${var} syntax in order to distinguish between $var2 and ${var}2.

In these cases, the 2 isn't part of any particular syntax per se, but on the contrary by using the curly-brace notation the 2 is being syntactically excluded from the variable reference.

0
2

When you use $variable, you're telling the shell, "Give me the value stored in the variable named variable." For instance:

name="Yesh"
echo "$name"

This will print: Yesh

When you use ${variable}2, you're saying, "Give me the value stored in variable and then add 2 right after it." For example:

name="Yesh"
echo "${name}2"

This will print: Yesh2

The curly braces, {}, are used to make sure the shell knows exactly where the variable name ends. This is important when you're combining a variable with other text. Without the braces, the shell might get confused and look for a variable name that includes the extra characters.

Example: Imagine you have a variable called file and you want to add a file extension to it:

file="document"
echo "$file.txt"     # This will be a problem because the shell will look for a variable named 'file.txt'
echo "${file}.txt"   # This will work correctly and print 'document.txt'

Using the braces helps the shell understand where the variable name stops and the extra text starts.

So, in short:

  • $variable: Just gives you the value of variable.
  • ${variable}2: Gives you the value of variable with 2 added to the end.
  • Using ${} helps avoid confusion and errors when you're mixing variables with other text.
2
  • 3
    Note that your example is misleading. Since a dot can not be used in a variable's name, the shell won't try to use a variable called file.txt if you say "$file.txt". Instead, it will give you the value in $file and add .txt to the end. It would be different if you had wanted to add some other string, starting with a letter or number, after $file.
    – Kusalananda
    Commented Jul 24 at 8:30
  • 2
    ...a letter or number, or an underscore. $foo_bar is different from ${foo}_bar. Which is sort of relevant in that it's visually different enough from letters and numbers that it might not be immediately obvious it doesn't work the same as a dot or a dash/hyphen.
    – ilkkachu
    Commented Jul 24 at 12:47
1

Maybe I’m missing something here, but it seems to me that

  • when you say
    grep -q $prefix*.TXT
    
    that expands as
    grep -q GLOBAL_AR_COLLECTOR_COMMENTS*.TXT
    
    which searches for GLOBAL_AR_COLLECTOR_COMMENT followed by zero or more occurrences of the letter S.  So it will match GLOBAL_AR_COLLECTOR_COMMENT, GLOBAL_AR_COLLECTOR_COMMENTS, GLOBAL_AR_COLLECTOR_COMMENTSS, GLOBAL_AR_COLLECTOR_COMMENTSSS, etc.
  • when you say
    grep -q ${prefix}2*.TXT
    
    that expands as
    grep -q GLOBAL_AR_COLLECTOR_COMMENTS2*.TXT
    
    which searches for GLOBAL_AR_COLLECTOR_COMMENTS followed by zero or more occurrences of the character 2.  So it will match GLOBAL_AR_COLLECTOR_COMMENTS, GLOBAL_AR_COLLECTOR_COMMENTS2, GLOBAL_AR_COLLECTOR_COMMENTS22, etc., but not GLOBAL_AR_COLLECTOR_COMMENT.

So, as long as the file doesn’t contain “GLOBAL_AR_COLLECTOR_COMMENT” (not followed by ‘S’), the search results will be the same.


... why we are using 2 with ${prefix}2?

It’s your code!  We can’t tell you why you wrote it that way!

3
  • 1
    They might rely on the fact that the globbing expands the full filename before grep kicks in, but it's hard to tell.
    – Kusalananda
    Commented Jul 24 at 21:45
  • The command grep -q anything* depends on whether there are at all, and if there are two or more, files with names starting with anything. If there are zero, grep will be fed the literal string anything*. If there are less than two but more than zero, globbing will expand the anything* into the name of the file, and search for that. If there are two or more, then grep will be fed the name of the first file (in glob order) as a search pattern to look for in the subsequent files. Commented Jul 25 at 4:55
  • I updated my answer with details regarding grep, and examples. Commented Jul 25 at 5:18
0

$variable expands to the contents of the variable.

$variable 2 expands to the contents of the variable, followed by a space, and 2.

$variable2 expands to the contents of a variable named variable2.

${variable}2 expands to the contents of variable, followed by 2

The pattern ${variable}2*.txt contains a 2 which must be matched. If variable contains foo then the pattern foo2*.txt is being matched: only directory entries beginning with foo2 and ending in .txt are considered.

If we remove the 2, ${variable}*.txt matches entries beginning with foo and ending in .txt. Because * isn't a variable name constituent character, we can then remove the braces to make $variable*.txt.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .