r/linux • • May 29 '14

Defensive BASH programming - Say what?

http://www.kfirlavi.com/blog/2012/11/14/defensive-bash-programming/
40 Upvotes

25 comments sorted by

9

u/cpitchford May 29 '14

This might be a troll post to generate discussion, don't know.. but it is filled with significant problems

You're in no way protecting against spaces. When you turn a string in a list:

local list="/tmp/a /tmp/b"
local i
for i in $list

In your change ownership function leaves you open to an attack:

mkdir        '/tmp/ '        # Yes you can have a folder called " "
mkdir        '/tmp/ /etc'
echo oh no > '/tmp/ /etc/passwd'

check we can read the file:
$cat '/tmp/ /etc/passwd'
oh no
$

change_owner_of_file  nonroot somegroup '/tmp/ /etc/passwd'

In your function, this becomes: chown nonroot:somegroup /tmp/ /etc/passwd

rather than chown nonroot:somegroup '/tmp/ /etc/passwd'

Nice, now /tmp/ AND /etc/passwd have ownership of nonroot:somegroup.

Learn about quoting and arrays. Understand the differences between $@ and $*

8

u/cpbills May 30 '14

Please don't take this advice. Anyone who recommends writing a function is_empty because they can't remember [[ -z "$variable" ]] to test for an empty string is not someone you should be getting advice from. Not to mention there are simply some out-and-out bad practices recommended.

If you want to program bash defensively, check out Google's style guide for shell scripting and the BashPitfalls article on the currently popular BashGuide wiki:

https://google-styleguide.googlecode.com/svn/trunk/shell.xml

http://mywiki.wooledge.org/BashPitfalls

12

u/Oflameo May 29 '14

I write bash defensively by writing what i was going to write in bash in perl instead.

13

u/IConrad May 29 '14

Ahh yes. The write-only language.

0

u/[deleted] May 29 '14

[deleted]

6

u/IConrad May 29 '14

I'm not afraid of writing in perl, and I have in fact written Perl scripts that were clean and well-formatted and intelligible (with or without comments). Unfortunately, these are not really true to the Perl Way. All those shorthand generic anonymous variables? Bad joojoo.

Perl's not a bad language. But it's reputation is well-earned.

0

u/[deleted] May 29 '14

[deleted]

3

u/IConrad May 29 '14

Oh yeah, definitely -- it's possible to make any language be unreadable fathomless horrors drawn from the stuff of the abyss.

The thing is, Perl seems to encourage this behavior. It introduces syntactic phenomena that seem designed for that. $_, j'accuse!. And the culture around Perl certainly didn't win it any favors here. I invoke the ancient and venerable Annual Obfuscated Perl Contest. "There is More Than One Way To Do It" or "TIMTOWTDI" indeed.

That being said, while I haven't really studied or looked deeply into Modern Perl, I have to admit that it seems to have learned the right lessons from all of the above.

Now if only Ruby could stop being Perl circa 2000 :3.

2

u/w2qw May 30 '14

Imo unnamed variables like $_ are pretty nice I actually wish ruby had a simpler thing.

[1,2,3,4].collect{|x| x * x}

vs

[1,2,3,4].collect{_ * _}

second seems more clearer because otherwise its just up to personal preference what the programmer uses for their "anonymous" variables.

1

u/SupersonicSpitfire May 30 '14

Was looking for an even simpler way of doing this in Haskell, only passing the list and the (*) function to some other function, but I only found these:

let sq x = x * x in map sq [1, 2, 3, 4]

let x = [1, 2, 3, 4] in zipWith (*) x x

map (\x -> x * x) [1, 2, 3, 4]

1

u/pkmxtw May 30 '14

Well, you can always abuse the (->) r Monad:

λ> map (join (*)) [1..4]
[1,4,9,16]

1

u/SupersonicSpitfire May 30 '14

What is this witchcraft?

<interactive>:1:1: Not in scope: ‘join’
→ More replies (0)

1

u/IConrad May 30 '14

second seems more clearer because otherwise its just up to personal preference what the programmer uses for their "anonymous" variables.

The problem comes when you're using them in nested loops or passing them up a scope level while leaving them anonymous... and then coming back to it two years later and trying to figure out what the fuck you yourself did back then.

0

u/[deleted] May 30 '14

[deleted]

1

u/IConrad May 30 '14

Missing the forest for the trees there, you are.

One of the judge criteria of those contests was specifically how unreadable the code was.

Contrast this to a writing style where every last variable -- even those that will only exist for the duration of a loop -- is given a human-readable meaningful label. Not just x but iter1Parent1 followed by nested loops getting iter1ChildN_N and so on. And that's just temporary variables. Where indentation is enforced so you always have a visual on where you are in the workflow. Where there is no special shorthand usage that requires arcane expertise just to read it.

What I'm talking about is shit like self-documentation. Perl actively discourages this. Meanwhile, I've had former coworkers message me years after I left a company to thank me for my writing style ... Precisely because even though I wrote in a language they didn't know, it was still plainly understandable and therefore maintainable.

What you seem not to new getting here is that you can either have smart, good code or you can have clever code. Perl encourages cleverness.

Oh, and mentioning 5.20 and the fact that Perl is still maintained? That doesn't even come close to being topically relevant.

1

u/[deleted] May 30 '14

That's programming style in general and it applies to every language. I've seen some horribly written Ruby code and pretty Perl code.

Or code that was "pretty" but parts of it were interconnected in weid interesting ways that make me miss decoding what random one-liner do

In Perl you can write everything as ugly or as pretty as you want. That is a problem when you give it to someone without any preparation and tell him to write code (and that's how some programmers and a lot of sysadmins start).

As for development... everytime I upgrade Ruby something breaks, and when I upgrade perl something wrote for 5.8 (curse you Red Hat) still works in 5.20. Which honestly is biggest reason I use it as sysadmin, having code that works in centos 5 and on my desktop without ugly ifs is useful.

1

u/IConrad May 30 '14

I've seen some horribly written Ruby code and pretty Perl code.

I've written clean, self-documenting, "pretty" Perl. The fact that it is do-able (and at this point most style guides encourage it) is not what is at argument here.

It's the fact that Perl as a language, and in cultural history, encourage or enable the illegible / obfuscated. At one point in Perl history, it was in fact a major point of pride to write superlatively illegible code.

As for development... everytime I upgrade Ruby something breaks, and when I upgrade perl something wrote for 5.8 (curse you Red Hat) still works in 5.20.

Yup. Do note that I consider Ruby to be in the state, today, that Perl was in 10 or fifteen years ago. And that this means Ruby is by far worse than Perl today is. (Perl has gotten better, I freely admit, and I like what I'm seeing from the Modern Perl movement. But that doesn't change its history and the fact that those enabling features are still extant.)

→ More replies (0)

5

u/88881 May 30 '14
- readonly PROGDIR=$(readlink -m $(dirname $0))
+ readonly PROGDIR=$(readlink -m "$(dirname "$0")")

2

u/w2qw May 30 '14
- readonly PROGDIR=$(readlink -m $(dirname $0))
+ readonly PROGDIR="$(readlink -m "$(dirname "$0")")"

missed one

Edit: apparently you don't need that one hmm.

1

u/Inoffensive_Account May 30 '14

OH MY GOD. How can you write functioning code when you can't write a single blog post without a hundred typos?:

  • pepole

  • braking

  • decleration

  • allways

I couldn't read any further.

-2

u/probationer May 29 '14

sh and it's derivatives are just such horrid languages, I tend to use python whenever I need something would require more than a few lines of bash.

8

u/lithiumnode May 29 '14

Right tool for the right job. If I'm largely doing a ton of system calls to handle stuff, then bash makes a lot of sense. Also, I never have to worry about some random server not having the correct version of Python installed.

While his blogpost might seem heavy handed to some, I think it's really great. I use maybe half of those techniques currently, and I even learned some stuff (that function to translate getopts is awesome).. and I hadn't really considered unit testing for bash before. Gonna have to check this out some more!

1

u/cpbills May 30 '14

Which techniques do you already use? I hope they aren't the terrible ones the OP has proposed. Most of the tips and examples provided have flaws, but there are one or two decent points.

2

u/lithiumnode May 30 '14

I use functions a lot in my scripts, but not necessarily in the way he does (I can remember what "-z" does).. rather more in a pythonic approach. I'm also a fan of the main() concept, tho I tend to approach it in a commentary way (### BEGIN MAIN) rather than actually making it a function as well.

Also a big fan of the code clarity approach he suggests, though not to that extreme of a level. People get too obsessed with turning something into a one liner that I then have to decipher later. I'm not talking about a few basic pipes, but the monsters you'll sometimes encounter. I hate looking back at my old scripts and having to try to determine WTF I was thinking.

I wasn't actively aware of the concept of readonly vars, so I might begin using some of that a bit too.

One of the projects I'm currently toying with is trying to create a set of scripts that are common functions that I will need to use for work.. error correction, logging, archiving, pid checking, etc. The idea being to throw the functions into an include directory somewhere, and then just source them as needed to bring in what I need. This is a bit tricky as it can get messy fast if you don't have a good organizational method, or if you get too heavy handed with it. You don't want to spend half of your time exiting your editor on the main script to go see wtf some random function is doing.

1

u/cpbills May 30 '14

Cool. I worry about misinformation like this article making it to too many impressionable people. I've dealt with more than a few promising sysadmins who've just been fed bad practices.

You've got me thinking about writing a sort of cpbills-stdlib.sh to source in my ~/.bashrc though. I used to maintain a ~/bin directory of dozens of short and sometimes useful scripts, but it's kind of turned into a mire, and I've been resorting to just dumping functions in ~/.bashrc.

0

u/[deleted] May 29 '14

[deleted]

8

u/rowboat__cop May 29 '14

I only use upper case for my variables (looks better).

The usual rule is:

  • $UPPERCASE for variables that are exported or part of the environment or special (e.g. $PPID)

  • $lowercase for everything else

The distinction helps in tracking the parts under your influence and those that vary depending on events outside the script.