DEV Community

Cover image for Bash Brackets Quick Reference
Ryan Palo
Ryan Palo

Posted on • Edited on • Originally published at assertnotmagic.com

Bash Brackets Quick Reference

Cover image credit: Fonts.com

Bash has lots of different kinds of brackets. Like, many much lots. It adds meaning to doubling up different brackets, and a dollar sign in front means something even more different. And, the brackets are used differently than many other languages. I constantly find myself doing a 5-second search for which one is the right one to do since I'm not writing Bash scripts all the time. So here, I'm going to lay them all out and then print this article out and staple it to the wall by my desk. Possibly with a decorative frame. So here we go.

A tiny note on all of these is that Bash generally likes to see a space between round or square brackets and whatever's inside. It doesn't like space where curly braces are concerned. We'll go through in order of net total squigglyness (NTS score).

( Single Parentheses )

Single parenthesis will run the commands inside in a subshell. This means that they run through all of the commands inside, and then return a single exit code. Any variables declared or environment changes will get cleaned up and disappeared. Because it's within a subshell, if you have it inside a loop, it will run a little slower than if you called the commands without the parentheses.



a='This string'
( a=banana; mkdir $a )
echo $a
# => 'This string'
ls
# => ...
# => banana/


Enter fullscreen mode Exit fullscreen mode

(( Double Parentheses ))

This is for use in integer arithmetic. You can perform assignments, logical operations, and mathematic operations like multiplication or modulo inside these parentheses. However, do note that there is no output. Any variable changes that happen inside them will stick, but don't expect to be able to assign the result to anything. If the result inside is non-zero, it returns a zero (success) exit code. If the result inside is zero, it returns an exit code of 1.



i=4
(( i += 3 ))
echo $i
# => 7
(( 4 + 8 ))
# => No Output
echo $?  # Check the exit code of the last command
# => 0
(( 5 - 5 ))
echo $?
# => 1

# Strings inside get considered 'zero'.
(( i += POO ))
echo $i
# => 7

# You can't use it in an expression
a=(( 4 + 1 ))
# => bash: syntax error near unexpected token '('


Enter fullscreen mode Exit fullscreen mode

<( Angle Parentheses )

Thank you to Thomas H Jones II for this comment that inspired this section on Process Substitution

You missed one of the really great BASHisms: <( COMMAND ). Basically, run command in a subshell, then return it's output through a file-descriptor. Meaning that you can do things like:

  • diff two streams
  • run a command within a shell to create an input-"file" for other commands that want input in the form of a file rather than a stream.

Another great one is VAR=($( COMMAND )) ...which takes the output from COMMAND and creates an array-variable from it.

Also useful is $( COMMAND )$? for when you care about how a command exited but not its output (e.g., you want to test to see if grep found a string in a file, [[ $( grep -q PATTERN FILE )$? ]].

This is known as a process substitution. It's a lot like a pipe, except you can use it anywhere a command expects a file argument. And you can use multiple at once!



sort -nr -k 5 <( ls -l /bin ) <( ls -l /usr/bin ) <( ls -l /sbin )

# => Like a billion lines of output that contain many of the
# => executables on your computer, sorted in order of descending size.

# Just in case you don't magically remember all bash flags,
# -nr  means sort numerically in reverse (descending) order
# -k 5 means sort by Kolumn 5.  In this case, for `ls -l`, that is the "file size"


Enter fullscreen mode Exit fullscreen mode

This works because the sort command expects one or many filenames as arguments. Behind the scenes, the <( stuff ) actually outputs the name of a temporary file (unnamed pipe file) for the sort command to use.

Another example of where this comes in handy is the use of the comm command, which spits out the lines that the files have in common. Because comm needs its input files to be sorted, you could either do this:



# The lame way
sort file1 > file1.sorted
sort file2 > file2.sorted
comm -12 file1.sorted file2.sorted


Enter fullscreen mode Exit fullscreen mode

Ooooor, you can be a total BAshMF and do it this way:



# The baller way
comm -12 <( sort file1 ) <( sort file2 )


Enter fullscreen mode Exit fullscreen mode

$( Dollar Single Parentheses )

This is for interpolating a subshell command output into a string. The command inside gets run inside a subshell, and then any output gets placed into whatever string you're building.



intro="My name is $( whoami )"
echo $intro
# => My name is ryan

# And just to prove that it's a subshell...
a=5
b=$( a=1000; echo $a )
echo $b
# => 1000
echo $a
# => 5


Enter fullscreen mode Exit fullscreen mode

$( Dollar Single Parentheses Dollar Q )$?

Shoutout again to Thomas for the tip!

If you want to interpolate a command, but only the exit code and not the value, this is what you use.



if [[ $( grep -q PATTERN FILE )$? ]]
then
  echo "Dat pattern was totally in dat file!"
else
  echo "NOPE."
fi


Enter fullscreen mode Exit fullscreen mode

Although, really, this isn't so much a special bracket pattern as it is an interesting use of $?, since the above works even if there is a space between the $( stuff ) and the $?. But a neat tip, nonetheless.

$(( Dollar Double Parentheses ))

Remember how regular (( Double Parentheses )) don't output anything? Remember how that is kind of annoying? Well, you can use $(( Dollar Double Parentheses )) to perform an Arithmetic Interpolation, which is just a fancy way of saying, "Place the output result into this string."



a=$(( 16 + 2 ))
message="I don't want to brag, but I have like $(( a / 2 )) friends."
echo $message
# => I don't want to brag, but I have like 9 friends."

b=$(( a *= 2 ))         # You can even do assignments.  The last value calculated will be the output.
echo $b
# => 36
echo $a
# => 36


Enter fullscreen mode Exit fullscreen mode

One thing to remember is that this is strictly integer arithmetic. No decimals. Look into bc for floating point calculations.



echo $(( 9 / 2 ))  # You might expect 4.5
# => 4

echo $(( 9 / 2.5 ))
# => bash: 9 / 2.5 : syntax error: invalid arithmetic operator (error token is ".5 ")


Enter fullscreen mode Exit fullscreen mode

[ Single Square Brackets ]

This is an alternate version of the built-in test. The commands inside are run and checked for "truthiness." Strings of zero length are false. Strings of length one or more (even if those characters are whitespace) are true.

Here are a list of all of the file-related tests you could do, like checking if a file exists or if it's a directory.

Here are a list of all of the string-related and integer-related tests you could do, like checking if two strings are equal or if one is zero-length, or if one number is bigger than another.



if [ -f my_friends.txt ]
then
    echo "I'm so loved!"
else
    echo "I'm so alone."
fi


Enter fullscreen mode Exit fullscreen mode

One last thing that's important to note is that test and [ are actually shell commands. [[ ]] is actually part of the shell language itself. What this means is that the stuff inside of Double Square Brackets isn't treated like arguments. The reason you would use Single Square Brackets is if you need to do word splitting or filename expansion.

Here's an illustration of the difference. Let's say you used Double Square Brackets in the following way.



[[ -f *.txt ]]
echo $?
# => 1


Enter fullscreen mode Exit fullscreen mode

False, there is no file explicitly named "[asterisk].txt". Let's assume there are currently no .txt files in our directory.



# If there's no files .txt files:
[ -f *.txt ]; echo $?
# => 1


Enter fullscreen mode Exit fullscreen mode

*.txt gets expanded to a blank string, which is not a file, and then the test gets evaluated. Let's create a txt file.



touch cool_beans.txt
# Now there's exactly one .txt file
[ -f *.txt ]; echo $?
# => 0


Enter fullscreen mode Exit fullscreen mode

*.txt gets expanded to a space-separated list of matching filenames: "cool_beans.txt", and then the test gets evaluated with that one argument. Since the file exists, the test passes. But what if there's two files?



touch i_smell_trouble.txt  # bean pun.  #sorrynotsorry
# Now there's two files
[ -f *.txt ]
# => bash: [: too many arguments.


Enter fullscreen mode Exit fullscreen mode

*.txt gets expanded to "cool_beans.txt i_smell_trouble.txt", and then the test is evaluated. Bash counts each of the filenames as an argument, receives 3 arguments instead of the two it was expecting, and blurffs.

Just to hammer my point home: even though there are currently two .txt files, this next test still fails.



[[ -f *.txt ]]; echo $?
# => 1.  There is still no file called *.txt


Enter fullscreen mode Exit fullscreen mode

I tried to come up with some examples of why you would want this, but I couldn't come up with realistic ones.

For the most part, it seems like, a good rule of thumb is: if you need to use test or [ ], you'll know it. If you're not sure if you need it, you probably don't need it and you should probably use [[ double square brackets ]] to avoid a lot of the tricky gotchas of the test command. If your shell is modern enough to have them.

[[ Double Square Brackets ]]

True/false testing. Read through the section above for an explanation of the differences between single and double square brackets. Additionally, double square brackets support extended regular expression matching. Use quotes around the second argument to force a raw match instead of a regex match.



pie=good
[[ $pie =~ d ]]; echo $?
# => 0, it matches the regex!

[[ $pie =~ [aeiou]d ]]; echo $?
# => 0, still matches

[[ $pie =~ [aei]d ]]; echo $?
# => 1, no match

[[ $pie =~ "[aeiou]d" ]]; echo $?
# => 1, no match because there's no literal '[aeoiu]d' inside the word "good"


Enter fullscreen mode Exit fullscreen mode

Also, inside double square brackets, < and > sort by your locale. Inside single square brackets, it's by your machine's sorting order, which is usually ASCII.

{ Single Curly Braces }

Single curly braces are used for expansion.



echo h{a,e,i,o,u}p
# => hap hep hip hop hup
echo "I am "{cool,great,awesome}
# => I am cool I am great I am awesome

mv friends.txt{,.bak}
# => braces are expanded first, so the command is `mv friends.txt friends.txt.bak`


Enter fullscreen mode Exit fullscreen mode

The cool thing is that you can make ranges as well! With leading zeros!



echo {01..10}
01 02 03 04 05 06 07 08 09 10
echo {01..10..3}
01 04 07 10


Enter fullscreen mode Exit fullscreen mode

${dollar braces}

Note that there are no spaces around the contents. This is for variable interpolation. You use it when normal string interpolation could get weird



# I want to say 'bananaification'
fruit=banana
echo $fruitification
# => "" No output, because $fruitification is not a variable.
echo ${fruit}ification
# => bananaification


Enter fullscreen mode Exit fullscreen mode

The other thing you can use ${Dollar Braces} for is variable manipulation. Here are a few common uses.

Using a default value if the variable isn't defined.



function hello() {
  echo "Hello, ${1:-World}!"
}
hello Ryan
# => Hello Ryan!
hello
# => Hello World!

function sign_in() {
    name=$1
  echo "Signing in as ${name:-$( whoami )}"
}
sign_in
# => Signing in as ryan
sign_in coolguy
# => Signing in as coolguy


Enter fullscreen mode Exit fullscreen mode

Getting the length of a variable.



name="Ryan"
echo "Your name is ${#name} letters long!"
# => Your name is 4 letters long!


Enter fullscreen mode Exit fullscreen mode

Chopping off pieces that match a pattern.



url=https://assertnotmagic.com/about
echo ${url#*/}     # Remove from the front, matching the pattern */, non-greedy
# => /assertnotmagic.com/about
echo ${url##*/}    # Same, but greedy
# => about
echo ${url%/*}     # Remove from the back, matching the pattern /*, non-greedy
# => https://assertnotmagic.com
echo ${url%%/*}    # Same, but greedy
# => https:


Enter fullscreen mode Exit fullscreen mode

Thanks Sanket Patel for the error fix!

You can uppercase matching letters!



echo ${url^^a}
# => https://AssertnotmAgic.com/About


Enter fullscreen mode Exit fullscreen mode

You can get slices of strings.



echo ${url:2:5}  # the pattern is ${var:start:len}.  Start is zero-based.
# => tps://


Enter fullscreen mode Exit fullscreen mode

You can replace patterns.



echo ${url/https/ftp}
# => ftp://assertnotmagic.com

# Use a double slash for the first slash to do a global replace
echo ${url//[aeiou]/X}
# => https://XssXrtnXtmXgXc.cXm


Enter fullscreen mode Exit fullscreen mode

And, you can use variables indirectly as the name of other variables.



function grades() {
  name=$1
  alice=A
  beth=B
  charles=C
  doofus=D
  echo ${!name}
}

grades alice
# => A
grades doofus
# => D
grades "Valoth the Unforgiving"
# => bash: : bad substitution.   
# There is no variable called Valoth the Unforgiving,
# so it defaults to a blank value.  
# Then, bash looks for a variable with a name of "" and errors out.


Enter fullscreen mode Exit fullscreen mode

<<Double Angle Heredocs

This is how you make multiline strings in Bash (one method). Two arrows and then a word -- any word that you choose -- to signal the start of the string. The string doesn't end until you repeat your magic word.



read -r -d '' nice_message <<MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
MESSAGE

echo "$nice_message"
# => Hi there!  I really like the way you look
# => when you are teaching newbies things
# => with empathy and compassion!
# => You rock!

# Note, if you store a string with newlines in a variable, 
# you have to quote it to get the newlines to actually print.
# If you just use `echo $nice_message`, it won't reflect newlines.


Enter fullscreen mode Exit fullscreen mode

The word can be whatever you want. I generally end up using "HEREDOC" to make it easier for future me.

The real ancient wisdom in the comments section from Ian Kirker:

A couple of things about HEREDOC bits:

  • HEREDOCs when used with a command are fed into stdin for a command. The way you've used it in your first example for assignment to a variable doesn't work, at least for me and my bash 4.4.19.
$ nice_message=<<MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
MESSAGE
$ echo $nice_message

$
Enter fullscreen mode Exit fullscreen mode
$ cat <<MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
$ 
Enter fullscreen mode Exit fullscreen mode
  • If you put single quotes around the initial tag, the HEREDOC will be treated like a single-quoted string, leaving variable expressions uninterpolated.
$ cat <<EOF
$RANDOM
EOF
4578

$ cat <<'EOF'
$RANDOM
EOF
$RANDOM
Enter fullscreen mode Exit fullscreen mode
  • You can also use triple less-than symbols without a tag to just feed in a single simple string, like so:
$ cat <<EOF
beep
EOF
beep

$ cat <<<beep
beep
$
Enter fullscreen mode Exit fullscreen mode

One final trick is that, if you add a dash after the arrows, it suppresses any leading tabs (but not spaces) in your heredoc.



cat <<-HEREDOC
two leading tabs
one leading tab
two spaces here
HEREDOC

# => two leading tabs
# => one leading tab
# => two spaces here

Enter fullscreen mode Exit fullscreen mode




Punctuation's a Killer

Hopefully this is helpful. If you see something I missed or have another cool use for one of these variants, be sure to let me know, and I'll update it and publicly praise your genius. Thanks for reading!

Top comments (46)

Collapse
 
ferricoxide profile image
Thomas H Jones II • Edited

You missed one of the really great BASHisms: <( COMMAND ). Basically, run command in a subshell, then return it's output through a file-descriptor. Meaning that you can do things like:

  • diff two streams
  • run a command within a shell to create an input-"file" for other commands that want input in the form of a file rather than a stream.

Another great one is VAR=($( COMMAND )) ...which takes the output from COMMAND and creates an array-variable from it.

Also useful is $( COMMAND )$? for when you care about how a command exited but not its output (e.g., you want to test to see if grep found a string in a file, [[ $( grep -q PATTERN FILE )$? ]].

Collapse
 
rpalo profile image
Ryan Palo

These are great! When I get in front of a keyboard, I’ll add them to the list! Thanks for sharing. What are the most common file-expecting commands you use the sub shell redirect with?

Collapse
 
ferricoxide profile image
Thomas H Jones II • Edited

AWS CLI for (particularly for CloudFormation) seems to not quite process STDIN for the --parameters flag as one might expect. Usually have to do something like what I put in my article when I'm doing stack-iterations as part of a quick test-change-test cycle (where "quick" is relative to things like RDS - there's frequently not enough time to wait for one launch to delete before moving on to the next launch).

Thread Thread
 
rpalo profile image
Ryan Palo

Thanks again for the tips! I just wanted to let you know that I updated the article with your suggestions. 😀

Collapse
 
kogans profile image
Stanislav Kogan

the $( COMMAND )$? trick is a very bad one. Please don't advertise it, it basically NEVER works as you intend it to.

Collapse
 
ferricoxide profile image
Thomas H Jones II

Not sure what your experience is, but mine is that it works pretty much exactly as one would reasonably expect. It's functionally equivalent to executing something like:

<COMMAND>

if [[ $? ]]
then
…

But without any shell-linters bitching about using an inefficient execution-form.

That said, you need to be familiar with what the subshelled-command's likely outputs are going to be. Which is to say:

  • If the subshelled-command has an output other than just an exit code, you need to suppress it. Some commands have a built-in "quiet" option; for those that don't, you can suppress by redirecting output to /dev/null. Failing to suppress output will tend to cause evaluation-logic to evaluate as a string of <COMMAND_OUTPUT><COMMAND_EXITCODE> rather than an integer of <COMMAND_EXITCODE>.
  • Similarly, if you care to handle more than a -eq 0 or -ne 0 output, you need to be familiar enough with the given command's possible exit-codes to set up the appropriate handlers you might want/need.
Thread Thread
 
kogans profile image
Info Comment hidden by post author - thread only accessible via permalink

You totally misunderstand what [[ $? ]] actually does. It is actually equivalent to [[ -n $? ]], with -n being the operator for non-empty string test. Since $? always contains an exit status of the last command, it will always be a non empty string, so [[ $? ]] will always return 0 and evaluate to true in if context.

Please read up on how if works in bash and stop screwing up noobs with worthless code.

 
ferricoxide profile image
Thomas H Jones II

Ok, so, I shortcut on my prior statement. The response was basically, "here's something you can start your Googling with," not, "here's a whole freaking book on the topic." But, congratulations, rage-boy, in your quest to service "noobs" (great to throw that term out there, btw: really shows your head-space and overall level of respect for your community-members), you've extended this thread beyond the scope of a simple Google-starter. And for all of that belaboring, the point still stands that there's many useful, reliable ways to use the $( command )$? construct. Clearly your opinion differs. Clearly you've been badly snakebitten (or otherwise simply have an axe to grind). However, like with any useful tool or construct, just because there are ways that it can fail within a given context, it's still useful when correctly used in the right contexts.

Thread Thread
 
rpalo profile image
Ryan Palo

Hi guys! Just FYI, I’ve muted and moderated both of your comments in this particular thread. Let’s try to keep it positive and helpful for the people who need this info next time. I’d appreciate it if you’d take any further posturing/bickering into dms and out of my comments. Thanks!

Thread Thread
 
kogans profile image
Stanislav Kogan

You have not demonstrated any useful way of using the $( command )$? construct. Your original one is dangerous and misleading. I'm asking you, please stop screwing over noobs - bash can be very unforgiving and can do a lot of damage when used incorrectly.

 
ferricoxide profile image
Thomas H Jones II

No worries. After my final reply, I did a "who is this guy" click on his profile noticed that he'd apparently created an account just to piss on the thread (created the day he started thread-crapping and only activity shown on profile is the thread-crapping). So, had no further intent to respond.

Thread Thread
 
kogans profile image
Stanislav Kogan

Yes, I'm new here, but I'm not new. I write a LOT of bash on my job, so when I see these dangerous "recommendations", it makes me cringe.
And you, my friend, should reign in your prideful graphomania and actually TEST your code before you post it.

Collapse
 
ikirker profile image
Ian Kirker

A couple of things about HEREDOC bits:

  • HEREDOCs when used with a command are fed into stdin for a command. The way you've used it in your first example for assignment to a variable doesn't work, at least for me and my bash 4.4.19.
$ nice_message=<<MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
MESSAGE
$ echo $nice_message

$
$ cat <<MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
MESSAGE
Hi there!  I really like the way you look
when you are teaching newbies things
with empathy and compassion!
You rock!
$ 
  • If you put single quotes around the initial tag, the HEREDOC will be treated like a single-quoted string, leaving variable expressions uninterpolated.
$ cat <<EOF
$RANDOM
EOF
4578

$ cat <<'EOF'
$RANDOM
EOF
$RANDOM
  • You can also use triple less-than symbols without a tag to just feed in a single simple string, like so:
$ cat <<EOF
beep
EOF
beep

$ cat <<<beep
beep
$
Collapse
 
rpalo profile image
Ryan Palo

Hm. I’ll take a look at that. Thanks! I was doing all of my local testing in Zsh, and forgot that there might be differences.

Collapse
 
rpalo profile image
Ryan Palo

I updated the post with your help and extra info! Thanks again!

Collapse
 
moopet profile image
Ben Sinclair

This is a really great overview. Probably the clearest thing I've ever read about substitutions and their ilk :)

One thing I might add to it would be just to flag which bits are POSIX. Like where you have the single- vs. double-square-brackets rule of thumb, you say that if you need to use test or [ you'll know it - well the main reason you'd know it is if you were wanting to write a script which might be portable, i.e. work in a foreign shell.

Collapse
 
rpalo profile image
Ryan Palo

Thanks! I’ll put something in there about that. I tried to avoid it, because I found during my research that articles that constantly focused on POSIX really distracted from the rest of the info. But you are right that it’s probably good to at least mention that it is a thing.

Thanks for the feedback!

Collapse
 
sanketjpatel profile image
Sanket Patel

Small fix for -
echo ${url%%/*}

It actually prints -
https:

Collapse
 
rpalo profile image
Ryan Palo

Ah! You're totally right. Let me get that fixed. Thanks!

Collapse
 
thewoolleyman profile image
Chad Woolley • Edited

Great summary.

I've written a ton of bash.

My rule for writing bash is to write as little bash as possible.

Anything with any sort of even mildly complex logic, use a modern scripting language - Ruby (my preference) or Python.

I've also written github.com/thewoolleyman/process_h... which makes dealing with subprocesses much nicer in Ruby.

Collapse
 
kogans profile image
Stanislav Kogan • Edited

My experience has been the exact opposite. Bash, when properly learned, is very powerful and, what's more important, SELF SUFFICIENT.
Ruby and Python either make for very long scripts to accomplish even the basic tasks or pull in a gillion libraries as dependencies, which turns it into a sysadmin nightmare. BTW, the process helper library you linked to is a classic example of that.

My personal preferences for scripting: bash when possible, Perl when necessary, Python if you're adventurous and NEVER Ruby.

Collapse
 
ajuliansr profile image
A Jul

Cobol forever (:-}

Collapse
 
david_326fe1e5 profile image
David Laštovička

Nice overview. The curly brackets can be also used to encapsulate function body.

say_hello() {
    echo hello
}
Enter fullscreen mode Exit fullscreen mode

And to concatenate output of several processes:

{ echo a && echo b ; } > concatenated.txt
Enter fullscreen mode Exit fullscreen mode
Collapse
 
thebeartiger profile image
John Harrington

Thanks for this valuable article. So many of us use bash scripting without ever properly learning it.

I want to point out that, in a Boolean context, the (( )) is an evaluation of the contained expression, not the exit code of the double parens itself. For example:

 if (( 2 - 2 )) # false (0)
 then
      echo hi
 else
      echo die
 fi
Enter fullscreen mode Exit fullscreen mode

Even though the exit code of (( 2 - 2 )) is 1 (true), this outputs die since the evaluation of the expression 2 - 2 is 0.

Note that (( (( 2 - 2 )) )) would, however, be false, since the evaluation of (( 2 - 2 )) is its exit code.

Collapse
 
ben profile image
Ben Halpern

Love this post. Bash has remained something I've been content to stumble around with instead of learning, but this got me intrigued.

Collapse
 
ferricoxide profile image
Thomas H Jones II • Edited

Heh... There's so much I can do with BASH that, when under a time-crunch, it's hard for me to justify figuring out "how do I accomplish 'X' in <INSERT_LANGUAGE_OR_DSL>" ...when I can just bang it out in BASH in like two seconds

Collapse
 
ben profile image
Ben Halpern

Yeah, I logically know all of this but have still maintained vast ignorance.

Collapse
 
rpalo profile image
Ryan Palo

Basically until I wrote this, I just guessed and then tried another set of brackets when one didn’t work. I do that with a lot of stuff in Bash, so I’m going back to fill in all the gaps.

Collapse
 
joecannatti profile image
Joe Cannatti

This is great! I use bash a ton but wasn't aware of several of these. Really useful.

Collapse
 
rpalo profile image
Ryan Palo

Thanks! Glad it was able to help you out!

Collapse
 
kogans profile image
Stanislav Kogan

It's nice to show off some tricks, but the amount of bad and downright DANGEROUS practices in this article is just too much. Really, sometimes it's just best to READ THE F-CKING MAN PAGE!!!

Collapse
 
rpalo profile image
Ryan Palo

Hi there! I found the man pages dense and opaque at times, which is why I put this article together. Are there any especially dangerous practices that you’d like to point out?

Collapse
 
kogans profile image
Stanislav Kogan

It's true that bash man page is a very dry reading, but once you get the basics figured out, it is definitely the best source. That's why it's important to use the correct terminology in your article - it will allow people to quickly find the necessary subjects in the man page.

Now, for the mistakes in the article.

  • "(( Double Parentheses ))" is actually a compound command that undergoes arithmetic evaluation. its the equivalent of "let".
# Not correct
a=(( 4 + 1 ))

# Correct
((a=4 + 1))
Enter fullscreen mode Exit fullscreen mode
  • "$( Dollar Single Parentheses )" is actually called "Command substition"

  • "$( Dollar Single Parentheses Dollar Q )$?" is an absoultely HORRIBLE trick, NEVER EVER EVER do this! Oh, and btw, this doesn't actually work, because, the few times that it won't spew out an error, it will always evaluate to true.

# This is very bad
if [[ $( grep -q PATTERN FILE )$? ]]; then
 ...

# Do this instead
if grep -q PATTERN FILE; then
 ...

# oh, and by the way
[[ $(false)$? ]]; echo $?
#=> 0
# thats because  $? is evaluated as a string, and "1" is non empty

[[ $(false) $? ]]; echo $?
#=> bash: conditional binary operator expected
#=> bash: syntax error near `$?'
# LOL
Enter fullscreen mode Exit fullscreen mode
  • "$(( Dollar Double Parentheses ))" is actually called "Arithmetic expansion" with the expression within undergoing "Arithmetic evaluation". An interesting fact: double paratheses is not the only place arithmetic evaluation occurs: it also occurs inside array subscripts, which can be a pretty surprising thing sometimes.

  • [ Single Square Brackets ]. This deserves a special caution: unless you require backward compatibility with classic sh, you should NEVER use it. It's error-prone, requires gillion of quotes to make reliable and spawns a separate process for no good reason. Also, "[[" has more features.

  • "{ Single Curly Braces }" is called "brace expansion".

  • "${dollar braces}" is called "parameter expansion".

Thread Thread
 
rpalo profile image
Ryan Palo

Yep, I’m aware of what these are all called. I wrote it this way because the target audience most likely wouldn’t, and I wanted to make it an accessible visual guide. That being said, your other notes are helpful, and, when I get a chance, I’ll do my best to add the warnings and caveats where they’re needed. Thanks for taking the time to walk me through your experiences!

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more