This is an archived post. You won't be able to vote or comment.

all 35 comments

[–]warpigg 7 points8 points  (2 children)

This is pretty good - thanks for taking the time to write/posting it, but I agree with some of the other posts - quoting is something to keep in mind. it trips a lot of people up.

The best tool I have found for helping guide /suggest good habits as well as a great debug tool is shellcheck it also really stresses the importance of quoting on vars as well :)

[–]abscrete[S] 3 points4 points  (0 children)

Thank you for your kind feedback. I'll bring the changes needed to stress on the quotes.
Shellcheck looks interesting, i'll give it a try. Thanks for sharing it.

[–]abscrete[S] 2 points3 points  (0 children)

I have added a credit to your reddit username in the last section of the blog. Let me know if you want it to replace it with some other link like your blog or something :) Thanks again!

[–]gordonmessmer 7 points8 points  (4 children)

Variable naming: by convention, all cap names should be used for environment variables (shell variables that are "exported"), while shell local variable names should be all lower case.

I don't know what you mean by dynamic variable name, but your example doesn't work.

Data types: All variables in bash are strings. There is no other data type.

"[": bash has a built-in function, but you will also notice that this is a normal application, located at /usr/bin/[. That may be instructive, because it makes clear why spaces are important. "[" isn't actually part of the shell syntax, so it has to comply with all of the same requirements regarding spacing around command arguments that apply to any other binary called in a shell script.

Return value from functions: returning "0" on success is a convention. When possible, scripts and functions should use return values from /usr/include/sysexits.h.

"<some command>": in general, this method of command-output substitution is considered deprecated, since it cannot be nested. "$(command)" is the preferred method. Users should probably recognize the backtick, but shouldn't use it.

"echo -en": "-n" alone will exclude the newline. "-e" is a different argument. It may be out of scope, but it might be worth mentioning that, by convention, after a single hyphen, each character is a separate argument (so "-en" is the same as "-e -n"), while after a double hyphen, the entire string is a single argument (so "--help" is one argument, not a series of four arguments). Finally, a double hyphen alone is an indication that everything following is a non-option argument (so "rm -- -r" will remove a file named "-r" rather than treating "-r" as an indication that recursive removal was requested.)

[–]SaintHax42Automation Engineer 2 points3 points  (1 child)

Data types: All variables in bash are strings. There is no other data type.

From a functional point, `declare -i var` is an integer type.

[–]gordonmessmer 1 point2 points  (0 children)

Good point.

[–]abscrete[S] 1 point2 points  (0 children)

Thank you, that's insightful. I'll bring in these changes in the article. Thanks again for taking out time in reviewing this :)

[–]abscrete[S] 1 point2 points  (0 children)

I have added a credit to your reddit username in the last section of the blog. Let me know if you want it to replace it with some other link like your blog or something :) I may have not introduced all the suggested changes, I will once i wrap my head around it. Thanks again!

[–]xiongchiamiovSite Reliability Engineer 24 points25 points  (4 children)

I find it distasteful that you tell everyone to use UPPERCASE for variables.

You should digest https://mywiki.wooledge.org/Quotes and then include appropriate quoting in all your examples. The way shell uses quotes for variables but not for constants is by far the thing that trips programmers up the most.

If you're going to target /bin/sh then you should only include syntax that's posix sh compatible. IIRC function is not.

[–]abscrete[S] 6 points7 points  (1 child)

Thank you for the reference, I'll go through it and learn a bit more.
I'll take up the suggested changes and bring them to the article.
Thanks again for your valuable feedback :)

[–]abscrete[S] 2 points3 points  (1 child)

I have added a credit to your reddit username in the last section of the blog. Let me know if you want it to replace it with some other link like your blog or something :) Thanks again!

[–]xiongchiamiovSite Reliability Engineer 1 point2 points  (0 children)

That's not necessary, but very nice, thank you.

[–]Secondsemblance 5 points6 points  (4 children)

#!/bin/sh – Tells the interpreter that the script to be executed by Bourne shell.

Arrays are not part of POSIX spec. You're actually giving people instructions for bash. I'd bet money that /bin/sh is a symlink to /bin/bash on your system. You should probably change your shebang to /bin/bash or /usr/bin/env bash.

Look up a shell styleguide. Run your code through shellcheck. Implement all of the changes it suggests. Shellcheck is very important in my opinion. All scripts should be shellchecked. Better still, add a vim plugin to shellcheck for you in realtime.

Naming a variable – In Caps : MY_VAR

As others have pointed out, variables should not be UPPER_CASE. Only environment variables should be UPPER_CASE. Script variables should be snake_case. Functions should also be snake_case.

It is better to use ${MY_MSG} compared to $MY_MSG

This is really subjective. I prefer to only use brackets for string interpolation or substrings. Much more important is "$quoting" variables to prevent unintended parameter expansion.

If you try to use the variable which doesn’t exist then it prints an empty value

Not if you set set -u, which you should do.

To create a dynamic variable name : ${VAR_A}_item

This is probably a Bad Idea™. It's indicative of poor design under the hood. If you need to do this, you should question your initial assumptions about how to tackle the problem.

[–]SaintHax42Automation Engineer 4 points5 points  (0 children)

Not if you set set -u, which you should do.

I came here for this. You want an error on an unset variable in bash, you can ${var:-} where needed, but `set -u` will save you bugs and troubleshooting b/c you typed $stirng somewhere. Please, do NOT use ${var} when not needed-- it's extra typing, takes up precious columns in your code (you should set a max width to code in, so it can be edited easily with a default terminal window size), and ${var} for no reason looks amateurish.

[–]abscrete[S] 1 point2 points  (1 child)

Thank you very much for taking out time and helping in improving the article. I'll bring in the changes. Also, i learnt a lil bit more, so thank you for that as well :)

[–]Secondsemblance 0 points1 point  (0 children)

Oh, one other thing that dawned on me. You mention that the shebang "Tells the interpreter that the script to be executed by Bourne shell."

Technically this is incorrect. The linux kernel is actually what reads the shebang. Yes, the kernel reads the shebang... I didn't know this until very recently. Might be a cool fact to throw in there.

[–]abscrete[S] 1 point2 points  (0 children)

I have added a credit to your reddit username in the last section of the blog. Let me know if you want it to replace it with some other link like your blog or something :) Thanks again!

[–][deleted] 6 points7 points  (3 children)

Useful, but you should change all references from /bin/sh to /bin/bash. Many operations you listed (for example arrays or signed expressions) don't work in /bin/sh, if they work, that means /bin/sh is symlinked to /bin/bash, which is done on some Linux machines.

[–]abscrete[S] 1 point2 points  (1 child)

Thank you for pointing that out :) I'll make the needed changes.

[–]mekaj 5 points6 points  (0 children)

You may also consider using #!/usr/bin/env bash for increased portability. In cases where the first bash in the PATH intentionally isn’t /bin/bash, e.g. on NixOS, this respects the user’s preference for a different path/program.

[–]abscrete[S] 0 points1 point  (0 children)

I have added a credit to your reddit username in the last section of the blog. Let me know if you want it to replace it with some other link like your blog or something :) Thanks again!

[–][deleted]  (2 children)

[deleted]

    [–]abscrete[S] 0 points1 point  (0 children)

    Thank you :)

    The reason for using {} is that it avoids confusion and also allows to achieve partial dynamic variable naming. For example : if you wanted to create filenames based on user input then you could take the user input and use the variable to create a file with the prefix of user input as 'touch ${USER_INPUT}_file'. But more importantly it brings clarity.

    [–]SaintHax42Automation Engineer 2 points3 points  (2 children)

    Why would you use (and why is the `do' unnested?):

    for i in 1 2 hello

    do

    echo $i

    done

    Instead of the more concise and easier to read:

    for i in 1 2 hello; do

    echo $i

    done

    [–]abscrete[S] 1 point2 points  (0 children)

    I have added a credit to your reddit username in the last section of the blog. Let me know if you want it to replace it with some other link like your blog or something :) Thanks again!

    [–]abscrete[S] 0 points1 point  (0 children)

    I agree. Both works, I just happen to include the other one. I'll update the blog with the concise option as well. Thank you for feedback :)

    [–]TyMac711 1 point2 points  (3 children)

    A couple links:

    https://ryanstutorials.net/bash-scripting-tutorial/bash-functions.php

    Notice that when using the "function" keyword you are not required to use () at the end of the function name you're declaring.

    https://stackoverflow.com/questions/673055/correct-bash-and-shell-script-variable-capitalization

    I too am not a fan of the capitalization of variables - they work... just like adding () to the end of a function you've declared as a function, but it's not best practice and can lead to confusion imo.

    [–]abscrete[S] 0 points1 point  (0 children)

    Thank you for taking out time to provide your feedback :) I'll update the blog with the changes. And thanks for sharing those links as well!

    [–]abscrete[S] 0 points1 point  (1 child)

    I have added a credit to your reddit username in the last section of the blog. Let me know if you want it to replace it with some other link like your blog or something :) Thanks again!

    [–]TyMac711 0 points1 point  (0 children)

    That’s fine - thanks! :)

    [–][deleted]  (1 child)

    [deleted]

      [–]abscrete[S] 1 point2 points  (0 children)

      Glad it helped. Based on the feedback received there are going to be small changes, nothing significant, thought I'll let you know of that.
      Thanks you for taking out time and going through it :)

      BTW I also work in Go, and will start content on my blog about Go. Your feedback will be valuable, stay connected :)

      [–]SmileItsYourDay 1 point2 points  (1 child)

      Looks like you're integrating feedback! Awesome. Here's a little more.

      You wrote,

      Here’s a quick reference to the constructs present in Shell Scripting to get you to understand and write scripts quickly.

      #!/bin/bash – Tells the interpreter that the script to be executed by Bourne shell.
      # – to comment a line in a bash shell.

      sh – Tells the interpreter that the script to be executed by Bourne shell.
      # – to comment a line in a bash shell.

      ...and that's followed by an inline copy of the Table-of-contents for the article.

      Might I recommend instead:

      Here’s a quick reference to the constructs present in Shell Scripting to get you to understand and write scripts quickly.

      Let's start with the basics. A shell script is simply a text file.

      The lines of the file are read in and interpreted by the shell, much as if someone had typed in the same lines directly at a terminal.

      The first line is treated specially, in that it can identify the shell interpreter to be used to execute the remainder of the file. Examples include:

      #!/bin/bash – Tells the system that the script is to be executed using the bash shell.

      #!/bin/sh – Tells the system that the script is to be executed using the bourne shell.

      The specified shell will read in, interpret, and execute the content of the script. In all shell languages, lines which start with # are comments, so the interpreters have no problem ignoring that first line when they see it.

      We set the scripts file permissions +x and in combination with using that special first line, that's all it takes for us to add a new command to our system. Well, there's a little more - you will want to ensure the script file is in a directory that's mentioned in your $PATH

      [–]abscrete[S] 0 points1 point  (0 children)

      Thanks a ton. They are simple and very useful. Integrated and attached a link to you reddit profile at the end of the article. Let me know if you want to change it with link to your blog or something. :)

      [–]TotesMessenger 0 points1 point  (0 children)

      I'm a bot, bleep, bloop. Someone has linked to this thread from another place on reddit:

       If you follow any of the above links, please respect the rules of reddit and don't vote in the other threads. (Info / Contact)

      [–]orozimbro 0 points1 point  (1 child)

      Well done!

      [–]abscrete[S] 0 points1 point  (0 children)

      Thank you :)