Writing Bash scripts can be challenging if you don't know the quirks and perks. In my mother tongue, we use the Yiddish word for quirks and perks; ...
For further actions, you may consider blocking this person and/or reporting abuse
About the ${} and lonely variables... I disagree with this exception.
Three reasons.
CONSISTENCY!!! To write clean and easy to read scripts one must write consistent code. Consistent style, consistent patterns, consistent tooling. This way a maintainer/reader won't have to switch to "oh, that's a different variable expression. wait, is that dollar sign a part of the thing, or is it to be expanded..? oh, it's expanded. Good. So it's a variable" mode.
READABILITY. Tightly coupled to #1. Moreover, it's A LOT easier to read script and know which parts are dynamic and which aren't when the dynamic ones have clearer visual contrast. The ${} expression introduces 3 characters that make a word look like a variable. $ only has 1 char. It's easier to spot that large clunky ${myvar} than $myvar in the code. Regardless whether the value is lonely, at the EOL/SOL, on the left or spelled in reverse (consistency again!).
SAFETY. Writing
local age=$1
or evenlocal age=${1}
is not the best idea. What if the function was called w/o the parameter? The fn logic will most likely break. I've seen oh too many mistakes like that ending up in production environments going down or even worse -- getting completely destroyed and requiring a full rebuild (means days-weeks of work). Don't do that! Whenever you're accepting and reassigning function parameters, verify whether you've been passed any. One way to do that is[ -n "${1}" ] || return 1
, but that's just tedious and inelegant to write the same block of code in every function. Bash expansion provides a very handy safety switch:local age=${1:?Age not provided}
. Or a fallback to default value:local age=${1:-30}
. This way you can easily control what your variables are. If the value wasn't provided, in first case the function will return 1 and print the error message to stderr "Age not provided". In the second case the fn will continue execution after auto-assigning 30 to the variable age (if ${1} is empty).How does that relate to safety? By writing ${myVar} you will get the habbit of always thinking "do I need to use bash's variable expansion here? Perhaps default values? Or failfast safety switches? Substitution perhaps..?". Back in the days I was using $ I used to forget to check what's inside the $1 and write failfast mechanisms. By using ${1} now my hand automatically adds the :? or :- and makes me think twice. Even if IDC if the value is empty - I always leave it as
local age=${1:-}
just to make it absolutely clear that this function can tollerate missing parameter(s).I think I should write my own post series about shell/bash scripting.....
I would reply to this comment properly, if you hadn't added the last part
Go for it.
If you have a different opinion, please, do share :) I'd like to know your opinion and motivation behind "lonely variables"
Unless newer versions of bash are playing fast and loose with the language, '
if [ "$USER_NAME" = "Julia" || ..' is an error. You could write if test "$USER_NAME" = Julia || ..., but the [ command requires that its final argument be ]. The || is not an argument.
Superb comment, I never use single
[ ]
brackets, so I'm less familiar with how it all works. I'll fix my answer to fit the proper syntax, thanks!So which naming convention shall I use inside a loop?
for _VAR in
? Thank you for the guide!The way I name it
Output
I usually name my iterator as
item
, even if it's aLIST_OF_ROWS
orARRAY_OF_THINGS
I usually pickitem
as my iterator (in any lang).So, as you can see, there's no clear preference, just go with what suits you, and if you find
item
to be good for you, go for itThank you for the reply! Guess a guide on arrays would also be super useful - the example you made right here was more clarifying than many web articles I have read...
I'll definitely cover arrays in my next blog post, thank you for the feedback, much appreciated!
Also, off the top of my mind: getopt for flags and parallel to avoid writing loops in scripts (I’m just being creative)
Would you believe me if I told you that I've never, ever, used
getopts
orparalllel
? 🙈 I Never had the need to ... I found better alternatives that were easier to "memorize".getopts - See bargs; A framework for creating a Bash CLI. You would expect me to use
getops
for the "Usage" menu ... But I haven't, I used other tricks to make it workparallel - I've never processed big files in Bash, if I need to go down this road, I'll probably use Go, where it's more inclined towards parallelism and threading. I do use
background jobs
, but mainly for short processes which don't require "blocks of data". Instead, I usebackground jobs
, which is simply adding&
to a command andwait
in the bottom of the file to wait for it to finish. For example "downloading 5 files in parallel" or "encrypting 10 files in parallel".It's also a matter of time/knowledge; if you know
getopts
andparallel
then use them. If you don't, feel free to pass it, as I haven't found it "mandatory", but that's me.I didn’t know about bargs! That’s precious advice, thank you!
this blog is impressive, makes me learn a lot
Thanks for sharing. Will follow and wait for more.
Kudos, will post the next part soon :)
I'm usually set environment variables for the Bash scripts (or any other executable) like:
$ USER_NAME="Julia" USER_AGE="36" good_vibes.sh
Depends on the context ...
For local usage, sure, that works great.
For CI/CD processes, usually, values/secrets are injected with env vars ...
My mindset - fetch default values from env vars. A matter of approach/opinion I guess
Me too. That's better than exporting them to the env.
sudo comment "Great stuff! Will wait for the next parts!"
You don't need sudo for this. 😉
Thanks! Much appericiated!
Nice! I'm happy to see that somebody agrees that bash is still relevant and is worth to learn.
Will you cover the readonly "qualifier"?
I'm using Bash across most of my projects, so for me it's definitely here to stay :)
Truth be told, I haven't used readonly in any script, ever. I guess it's best practice to create immutable variables with:
Looks cool and easy to implement, I might adopt it in my future scripts.
Really nice article, but some explanation is needed.
Why?
@zoulja Good point!
I prefer using named variables instead of positional arguments. So the logic of my code is based on names instead of indexes, such as
$1
,$2
and so on. This way, even if the order of given arguments is changed, I still maintain my function's logic.And of course, let's learn by example; assuming we create the
greet()
functionNow, I want to change the positional arguments, so the function consumes
age
and thenname
I hope that explains it
Great timing! I was just looking into creating a couple scripts today.
Glad I could help!
Can I have your editor's color profile?
Well .. As you can those are code blocks, not screenshots, so the coloring comes from dev.to Markdown CSS.
To make code blocks beautiful in Markdown, simply add the relevant lang at the beginning of the code block, like:
I added
bash
right after the first 3 backticks `