Announcement

Collapse
No announcement yet.

What is your favourite Bash style?

Collapse
This topic is closed.
X
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    What is your favourite Bash style?

    I have been messing about with Bash programming now and then, but there are so many ways to accomplish the same thing, most notably in the aspect of branching.

    And I'm pretty new to all of that, never done much Bash programming before, or any real shell scripting.

    So the most common way to approach a branch is the if statement.

    Code:
    if [ $result -eq 0 ]; then
      command
    fi
    But the funky thing about bash is those && and || statements or stuff.

    Code:
    [ $result -eq 0 ] && iftrue || iffalse
    And it is so very tempting to use those constructs to make your code more terse, even if it makes it more hard to read:

    Code:
    some-command && connection="yes" || {
      things-to-do-when-there-is-no-link
    }
    
    [ $connection ] || {
      more-things-to-do-to-set-up-a-link
    }
    The weird thing about Bash or sh in general perhaps is that booleans do not easily evaluate to true, and any integer variable that is set, reguarly evaluates to true, and only an empty string evaluates to false

    So you get ugly code like "if [ $var == false ]; then" and the like??

    I don't know, I am new to it, I just sometimes spend hours inside "man bash" ;-).

    It is very easy to construct your scripting in a flat level style with lots of exits:

    Code:
    [ condition ] || exit 0
    
    [ condition2 ] && exit 1
    
    more-code-that-is-meant
    to-be-executed
    as-part-of-the-main-function-body
    or script-body
    On the other hand, personally I prefer code with pathing and single points of exit, usually:

    Code:
    if [ condition1 ]; then
      if [ condition2 ]; then
        result=x
      else
        result=y
      fi
    else
      skip=true
    fi
    I find it hard to do these sort of constructs in Bash.

    Currently I am attempting to ensure that the proper exit states (return codes, exit codes), make it to the appropriate function or script end:

    Code:
    ( exit $result; ) # this executes the exit in a subshell causing result to become the new exit status
    Usually currently with my expertise this often means a lot of redundant extra checks that have been made before.
    Last edited by xennex81; Feb 28, 2015, 03:37 PM.

    #2
    I truly prefer code to just branch towards the appropriate end.

    Even in e.g. java I experiment with ways to use e.g. exception catching to ensure that there is a way to exit a function that does not truly exit a function. An error status can mean a different kind of exit than the regular return values allow for. At the same time throwing e.g. an exception can mean certain variables remain ill set or ill defined, and a proper exit may need to fix those. If these variables are more globally scoped. But there are no goto statements, so often what you end up with is setting some flag and then continually testing for those flags along the road for a proper path. Which again can be quite ugly and perhaps too verbose to be wanting to do it for longer periods.

    In java you can even throw an exception, catch it, and rethrow it for the enclosing caller to handle it again. Or throw a new one of the same type (you cannot change an exception's message after it was created) but currently (since 1.7, apparently) you can at least catch multiple exceptions in the same "catch" block and rethrow the one you have caught. It has ugly syntax though:

    Code:
    } catch (SecurityException | NoSuchFieldException e) {
    and apparently it allows for descendent throwing or whatever without having to declare each type, I can't find the Oracle documentation on it currently. I believe it was part of some tutorial in the java docs.

    The difficulty in that (in Java) though is the duplicate code:

    Code:
    try {
      do-alot;
    
      if (finalize) {
        fix-variables
      }
    } catch (Exception e) {
      fix-variables
    } // how to execute the same finalization code in the "function succes" and the "function error" but not in the "have to do more on next invocation" ??
    I guess it is just an aspect or issue of improperly using a function as an iterative thing (retain states, process data on each invocation, return "big result" when session is finally completed) while not clearly defining what its result should be (just some status about what it is doing, what happens to the big result?) in combination with still wanting to handle the exceptions at the top level (?) as if the function is a "user level" thing (exposed and hence requiring proper error handling). I don't think you can combine the "object oriented" exception style with "low level" error states. We see the same in PHP, where OO is a newly added construct that does not really agree with the lower level function (or functional) programming style and user functions.

    You can use PHP OO to wrap those lower level functions to make them more attractive, but they are really your own creations and it is really a wrapper for the real thing, often.

    Have to go. Cya.
    Last edited by xennex81; Feb 28, 2015, 03:39 PM.

    Comment


      #3
      This is cool. What kind of scripts have you been writing?

      The behaviour of booleans is unpredictable becasue BASH isn't strongly typed. In strongly typed languages like C++ you would explicitly define a variable as a boolean, and then it could only have two possible values (1,0), but in scripting languages things can get hazy because variables are often converted between types on the fly. Learning a strongly typed language is a great exercise just because it makes you think about your variables more. Going back to a weakly typed language afterwards feels weird! It's much faster to write usable code in weakly typed languages though.

      As for tersity vs clarity, I would go for clarity every time, especially if someone else is going to read and use your code. Use lots of comments too! Disk space is cheap, so a few extra lines in a text file won't harm anyone, but there's no guarantee you'll understand your own code when you come back to it after a few weeks or months, let alone someone else's.
      samhobbs.co.uk

      Comment


        #4
        I am mostly trying to write scripts that replace NetworkManager at the moment . But in the meantime I just experiment with these styles because finding a proper thing that suits you can mean a lot of efficiency down the road.

        So basically what I've been doing lately when my internet was down (and still is, this phone has wifi but the laptop can't obtain it currently) (internet is a rare commodity here) was and is to automate all of the network stuff when bringing up a (wifi) link while ensuring security (this laptop might fall into the wrong hands and I may not be able to "plausibly deny" my passwords). So what I do is check for an internet link, see if I need to accept some hotspot license agreement, then advance with setting up tunnels and the like. It means I just need to automate some task that happens regularly. Nothing fancy, just what you'd expect a computer engineer to do ;-). I want to be able to work on my home server while the world thinks I'm a bad guy. Hence some need for precautions.

        The tersity is an advantage if you can achieve some level of (higher) expressibility with it. In other words, if the regular if then else or elif statements prove to be too limited for a shell scripting language... then I guess and it also depends on the difficulty in Bash of using the results of programs.

        If each and every time you have to do:

        Code:
        program
        result=$?
        if [ $result -eq 0 ]; then
          ..
        fi
        That is just a waste of time imho. Because the same thing is done with

        Code:
        program && ... || result=$?
        also. But it is hard to choose which happens first... && and || have equal precendence. But you can use { .. ; } and ( ... ) to create command blocks (lists).

        I know disk space is cheap . Disk space for documentation is especially cheap .

        I wish there was more cheapiness about disk space even more so I did not have to remove and resize and move my logical volumes all the time . ;-).

        I really like documenting though. Ever since I started programming at the age of nothing I've loved writing documentation . But it is usually something that happens when you are done with a certain piece. The difficulty, or, rather perhaps, the challenge , is to start writing it before you lose the concentration on it. That is the critical phase . In between having finished something and abandoning it hahahaha.

        If you screw that up then yes you end up with your own code you cannot read anymore haha.
        Last edited by xennex81; Feb 28, 2015, 03:52 PM.

        Comment


          #5
          I agree about the productivity thing when going from weakly typed to strongly typed though. I find my productivty in that sense being much higher in PHP than in Java.

          In Java there are 19 base types and 19x19 different conversion. Many of those are simple casts, but still quite a lot are difficult to remember.
          Remembering every form of int i = Integer.parseInt(string) is not easy. Or something you'd ever want to really do in full ;-).

          I must say about Bash I have really actually concluded now that the "if" statement is useless .

          It is too difficult to use. Seriously.

          Conditionals are too difficult for it since there ARE booleans in BASH. 0 means true, non-0 means false. But it is hard to use it in an if statement.

          The && and || naturally use those values for branching, but if does not. I even find it extremely annoying (and many do) that there is no real good way to test for "null" in Java. Then they design weakness around it and they have implemented a thing from Haskell (?) called an "optional" type. It is nothing different actually than the "null" type from PHP. If something is "optional" it is simply undefined, but Java turns it into objects. Instead of the alternative, which was:

          Code:
          String version = computer?.getSoundcard()?.getUSB()?.getVersion() ?: "UNKNOWN";
          from "Groovy".

          With a bit of a different syntax, it would have been very nice in Java indeed, I believe!!!!!!!!!!!!!!!!!! (Cannot use too many exclamation marks, I find every single decision in Java since (and beginning with) generics to be really really dysfunctional and ugly and improper.)

          I truly wish java would let null/not-null simply evaluate to a boolean and be done with that for the time being ;-).
          Last edited by xennex81; Feb 28, 2015, 04:46 PM.

          Comment


            #6
            Is it something that NM won't do or are you just interested in learning how to do it yourself?

            Originally posted by xennex81 View Post
            I really like documenting though. Ever since I started programming at the age of nothing I've loved writing documentation . But it is usually something that happens when you are done with a certain piece. The difficulty, or, rather perhaps, the challenge , is to start writing it before you lose the concentration on it. That is the critical phase . In between having finished something and abandoning it hahahaha.
            You hit the nail on the head there! I'm another documentation fan, and I know exactly what you mean.

            I've not used Java myself so can't really put what you just said into context. Can appreciate the frustration though!
            samhobbs.co.uk

            Comment


              #7
              Well, in my experience NM has a habit of bugging out. It is also not very respectful of existing connections when you activate it or when it is active already, you cannot do anything yourself because it will constantly delete your IP addresses etcetera.

              Comment


                #8
                Originally posted by xennex81
                I must say about Bash I have really actually concluded now that the "if" statement is useless.
                It appears you are using single square brackets, which are in fact equivalent to using the "test(1)" command, as in the original Bourne shell. The semantics can be frustrating, as it's like running an executable with normal command line processing.
                IMO, don't use "[". You only see a lot of it because GNU system shell scripts don't code to bash, being too large and bloated or something. My Utopic says /bin/sh is dash.

                Rather, with bash, use [[ and ]], and get reasonable, well documented (sort of, see the online manual) semantics. The perlish [[ something ]] && echo foo behaves better too. Also, if any arithmetic is involved, (( )) is much cleaner, f.ex.
                Code:
                    let foo=123456789 bar=3607
                    if (( foo % bar == 0 )); then
                        echo divides
                    fi
                Regards, John Little

                Comment


                  #9
                  Thanks, I'll give it a shot right away. I have noted before that very simple things would not work without the #!/bin/bash.

                  And I don't remember what it was. But it was something to really make you wonder.

                  I had not understood that there were so many difference between [] and [[]]. I only had found that [[]] would allow for a [[ $string == start* ]] as a form of prefix matching.

                  Comment


                    #10
                    For what it's worth, for any script that I am writing that is over 10 lines or so, I strongly consider writing in Python rather than bash.

                    Comment


                      #11
                      Can you term the advantages for that? For someone not intimately acquinted with Python?

                      And what are the advantages in terms of ease of modification and distribution?

                      By the way, [[ ]] didn't produce any changes in the handling of booleans and integers and so on, but I haven't checked the help (man) file yet for good measure in that measure ;-).
                      Last edited by xennex81; Mar 03, 2015, 09:14 AM.

                      Comment


                        #12
                        Python is a mature scripting language which encourages readability. It has object oriented features, excellent string manipulation, extensive modules which include regular expressions, socket communication, http communication, GUI (bindings for QT and GTK), and many other useful modules. Another big bonus is the ability to debug scripts and step though execution one line at a time.

                        Python's readability is enforced though mandatory indentation rules and the discouragement of multiple executable statements on a single line.

                        There are python interpreters for Linux, Windows, and Mac.

                        One example of a simple but helpful script that I've written is a script I call "timestamp". The script takes either an integer timestamp or a "date string" and prints the unified output to stdout. E.g.
                        Code:
                        $ timestamp 1425402402 '2014-03-03T09:06:42' 'Mar 3, 2013 09:06:42'
                        1425402402  Tue, Mar 03, 2015 09:06:42 (PST) Tue, Mar 03, 2015 17:06:42 (UTC)
                        1393866402  Mon, Mar 03, 2014 09:06:42 (PST) Mon, Mar 03, 2014 17:06:42 (UTC)
                        1362330402  Sun, Mar 03, 2013 09:06:42 (PST) Sun, Mar 03, 2013 17:06:42 (UTC)
                        There are times when I'm reading log files that I need to convert the Linux timestamp to a human readable date or convert a date to a timestamp. This script helps a lot.

                        I can share this script if anyone wants to see it.

                        Comment


                          #13
                          Originally posted by andystmartin View Post
                          Python is a mature scripting language which encourages readability. It has object oriented features, excellent string manipulation, extensive modules which include regular expressions, socket communication, http communication, GUI (bindings for QT and GTK), and many other useful modules. Another big bonus is the ability to debug scripts and step though execution one line at a time.

                          Python's readability is enforced though mandatory indentation rules and the discouragement of multiple executable statements on a single line.

                          There are python interpreters for Linux, Windows, and Mac.
                          I don't think you could have given a much more helpful or succinct summary than that!

                          I can share this script if anyone wants to see it.
                          Sounds interesting, please do! Python is something I've never tried but had been bumped up my list quite a bit recently...
                          samhobbs.co.uk

                          Comment


                            #14
                            Originally posted by andystmartin View Post
                            For what it's worth, for any script that I am writing that is over 10 lines or so, I strongly consider writing in Python rather than bash.
                            I second this ... and the explanation given!
                            I'd rather be locked out than locked in.

                            Comment


                              #15
                              Sounds interesting, please do! Python is something I've never tried but had been bumped up my list quite a bit recently...
                              OK, here it is. The script uses the datetime module and the dateutil module to manipulate time objects, use timezone information, and parse time integers or time-like strings. I have created a Time class that can convert integers or time-like strings to a datetime object (convert_to_datetime method). Time also has a format_datetime method for printing the datetime object in the localized format. Lastly, the script loops around the arguments passed on the command line printing one line per argument:
                              timestamp localtime UTCtime
                              .

                              Hope this helps.
                              Code:
                              #! /usr/bin/env python
                              
                              import time as _time
                              from datetime import datetime
                              import dateutil.parser
                              from dateutil.tz import tzfile
                              import sys
                              
                              class Time(object):
                                  def __init__(self):
                                      self.tz = tzfile('/etc/localtime')
                                      self.utc = tzfile('/usr/share/zoneinfo/UTC')
                                      self.dt = None
                              
                                  def convert_to_datetime(self, timeStr):
                                      try:
                                          # If the string is an integer, it's a timestamp
                                          timestamp = int(str(timeStr), 0)
                                      except ValueError:
                                          # Try parsing with dateutil
                                          try:
                                              # self.dt = dateutil.parser.parse(timeStr)
                                              self.dt = dateutil.parser.parse(timeStr).replace(tzinfo=self.tz)
                                          except ValueError:
                                              # Unable to parse time string or convert to int
                                              raise
                                      else:
                                          self.dt = datetime.fromtimestamp(timestamp, self.tz)
                                  
                                  def format_time(self, utc=False):
                                      timeStr = ''
                                      if self.dt is not None:
                                          dt = self.dt
                                          if utc:
                                              dt = dt.replace(tzinfo=self.utc) - dt.utcoffset() 
                                          timeStr = dt.strftime('%a, %b %d, %Y %H:%M:%S (%Z)')
                                      return timeStr
                                      
                                  def get_timestamp(self):
                                      t = 0
                                      if self.dt is not None:
                                          t = int(_time.mktime(self.dt.timetuple()))
                                      return t
                              
                              def main():
                                  now = int(_time.time())
                                  t_manip = Time()
                              
                                  ts = sys.argv[1:]
                                  if len(ts) == 0:
                                      # With no arguments, print current time.
                                      ts.append(now)
                              
                                  for s in ts:
                                      try:
                                          t_manip.convert_to_datetime(s)
                                      except ValueError as e:
                                          print "* Exception (%s): '%s'" % (str(e), s)
                                          print '  must be integer or yyyy-mm-ddThh:mm:ss string'
                                          continue
                              
                                      print '%10d ' % t_manip.get_timestamp(),
                                      print t_manip.format_time(),
                                      print t_manip.format_time(utc=True)
                              
                              if __name__ == '__main__':
                                  main()

                              Comment

                              Working...
                              X