Live data from Hacker News

Things I Wish I'd Known About Bash

zwischenzugs.com

211–220 of 272 posts

Re: Things I Wish I'd Known About Bash

#211
post #84

Earlier quoted context omitted.

If foo has a Texinfo manual (GNU tools like bash usually do) then you can try `info foo` and search the index with i or I for -p. Texinfo manuals also have hyperlinks you can press enter on. info is a greatly underused system and I'd recommend any *nix users to spend some time learning how to navigate it.

Thanks. Is there a good way to open that in a browser, rather than a console?

In addition to the HTML info pages hosted by GNU[1], a variety of GUI texinfo readers are available, such as tkinfo[2].

[1] https://www.gnu.org/software/bash/manual/html_node/index.htm...

[2] http://math-www.uni-paderborn.de/~axel/tkinfo/

Re: Things I Wish I'd Known About Bash

#212
9) The remote bash startup order is further complicated by the existence of a compile time flag SSH_SOURCE_BASHRC. This flag determines if a remote non-interactive shell will load the ~/.bashrc file.

This flag is turned off by default and stays off in some distributions (like Archlinux), but is turned on in others (Debian, Fedora, ...) to replicate very old rsh behaviour.

Re: Things I Wish I'd Known About Bash

#213

Earlier quoted context omitted.

A brand new macbook pro will not have bash4.

And it's all 2 minutes to add it as your default shell (including installing brew itself). 22:06 ~ $ bash --version GNU bash, version 4.4.12(1)-release (x86_64-apple- darwin16.3.0) Much easier than constraining oneself about what to put in one's script (assuming one is indeed targeting Linux, OS X etc released in the last 10+ years and not some embedded etc platforms).

If your project says it requires something from brew thats a blocker for a number of people.

Re: Things I Wish I'd Known About Bash

#214

Earlier quoted context omitted.

Well put it this way: You can assume everyone has a modern bash, and make it the end users problem if they don't, or you can write portable shell scripts and know it will work. Honestly the things you can't do in posix shell compared to bash border on "use a fully featured language" anyway.

How do you write a portable shell script? The programs invoked by your shell script need to behave the same everywhere. Even fundamental things like cp, rm, etc. don’t universally behave the same across the various Unix and Unix-like systems. The joke is that your shell is actually more portable than your shell script :)

Most of the shell utilities described by posix have standard flags, and then gnu/bsd extra flags.

If you use the standard ones (and use the "posix mode" flags when available) you're mostly ok.

Also, a shell script can have logic to handle different tools available (either different flavours of the same tool or even different tools that do similar things).

If the basic syntax it uses (or the shebang) are bash specific then you need bash to run it.

Re: Things I Wish I'd Known About Bash

#215
I didn't see it mentioned, but I use it frequently so here is my tip. I'm not sure if it is bash specific (I just use it!).

Instead of typing !$ for the previous command's final argument, you can use the keyboard shortcut alt+. (alt+period). Pressing it multiple times will go to the last argument of previous commands. I use this quite a bit and found it easier than !$, because you can see which command it will be : )

I still don't always understand exactly what is happening with subprocesses vs subshells (chriswarbo's post is very useful in pointing out how wrinkly this can be), so I try and keep bash my usage simple.

Re: Things I Wish I'd Known About Bash

#216

re 9) I feel better about always feeling at least slightly confused about what files are being sourced. The graph included seems to originate from https://blog.flowblok.id.au/2013-02/shell-startup-scripts.ht...

That graph is not entirely correct: https://news.ycombinator.com/item?id=16088866

Re: Things I Wish I'd Known About Bash

#217
post #62

Earlier quoted context omitted.

I disagree. Here's how I decide: Do I need to manipulate rich data structures like hash-maps or nested lists? That sort of thing tends to stretch the capabilities of Bash to its limits and I tend to set the bar fairly low here. Is the program oriented around commands? If I'm gluing executable scripts and binaries, using bash is often superior to a scripting language. Argument passing is more natural and convenient an…

As someone who has to occasionally modify 100+ line bash scripts written by Coworkers from Christmas Past which matched your spec in terms of what they had to do, please please just use Python (or similar). Yes, you will have a few extra lines but it will be vastly more readable and maintainable. And yes, I know I will get the standard the person who wrote the script did a bad job but at some point it should be okay…

> As someone who has to occasionally modify 100+ line bash scripts written by Coworkers from Christmas Past which matched your spec in terms of what they had to do, please please just use Python (or similar).

As someone who has inherited thousand-line shell scripts, and had to debug many 3rd party scripts, I stand by my assertion.

> Yes, you will have a few extra lines but it will be vastly more readable and maintainable.

Readability is important but it's not the only aspect to maintainability, nor is maintainability to sole concern of a tool. A low bug rate helps maintainability and actually having the features you need, in an acceptable timeframe, is also important.

For example, the OP mentioned the 'set -e' option that causes the script to exit if any command returns a non-zero exit code. In Python, you'd either have to remember to check the return code for every subprocess or define a wrapper, which adds complexity, reducing readability and can lead to bugs and errors. Nor is Python always the best answer for readability anyway. In many cases, it's not like it's just a few lines you're saving. Here are some functions I've used when scripting in Python

    import subprocess, shlex
    def process_run(cmd_string, stdin=None):
        return subprocess.Popen(shlex.split(cmd_string),
                                stdin=stdin,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
    
    def process_results(process_object):
        (stdout, stderr)=process_object.communicate()
        return (process_object.returncode, stdout, stderr)
    
    def process(cmd_string, stdin=None):
        return process_results(process_run(cmd_string, stdin=stdin))
It's 10 lines of boilerplate to set up an approximation of behavior that is trivial to achieve any shell language. There's actually 7 more functions I use to handle different common subprocess execution patterns. For example, the "stdin" in that process_run function needs to be a filehandle (at least in Python 2.7, I'm not sure about python 3). To pass a string to standard input you'll need something like this:

    f=SpooledTemporaryFile()
    f.write(stdin_string)
    f.seek(0)
    results=process(cmd_string, stdin=f)
    f.close()
    return results
> And yes, I know I will get the standard the person who wrote the script did a bad job but at some point it should be okay to blame the tools instead of the workman if workmen disproportionately create worse results with a set of tools.

Actually what I'd say first is that it's quite possible the person writing the script knew what they were doing. I've inherited bad code in my life, I've inherited some real gems, and I've inherited a lot of code in between. One thing I've learned is that I tend to be unfairly critical of average code. It's hard to read unfamiliar code and easy to criticize inconvenient design choices when you have to adapt their code to some new problem that they never anticipated. Usually I'll be better off just buckling down and untangling the spaghetti.

Re: Things I Wish I'd Known About Bash

#218
post #72
post #59

This is the most helpful bash diagram ever... why didn't I search for this before! https://zwischenzugs.files.wordpress.com/2018/01/shell-start...

As given in the article, it's also the most annoying bash diagram ever...it needs an explanation of what the 7 different colors of arrows mean. All that was given is: > It shows which scripts bash decides to run from the top, based on decisions made about the context bash is running in (which decides the colour to follow). > So if you are in a local (non-remote), non-login, interactive shell (eg when you run bash its…

This diagram is not entirely correct.

The remote bash startup order is further complicated by the existence of a compile time flag SSH_SOURCE_BASHRC. This flag determines if a remote non-interactive shell will load the ~/.bashrc file.

This flag is turned off by default and stays off in some distributions (like Archlinux), but is turned on in others (Debian, Fedora, ...) to replicate very old rsh behaviour.

Re: Things I Wish I'd Known About Bash

#219
Something I wish people teaching intermediate or advanced Bash tricks would emphasize more is how to make your program compatible with other shells. With the rise of Zsh's popularity, and the switch to Dash for Ubuntu/some Debian derivatives, I see a lot of people repeating bashisms in code they share without the knowledge that a) their code may not work for an unexpectedly large number of people, and b) switching to compatible equivalents doesn't make their code worse or less performant in many/most cases.

The most common bashisms and ways to avoid them are:

- Double brackets ([[) around conditions. Yes, I know that [ is a program (don't believe me? "which ["). That doesn't mean Bash uses it; it uses a builtin which is (almost) equivalent to [[ instead. Use that and your code will work in zsh/dash/all other POSIX shells. And while you're at it, stop using "which" as an authority for "is this a shell builtin or not?" [type()](http://linuxcommand.org/lc3_man_pages/typeh.html) is your friend.

- When comparing strings for equality, use a single equals sign "=", not "==" (e.g. 'if [ "$foo" = "some string" ]'). I know it feels dirty if you've programmed in any other language, but it changes nothing about your code's behavior and makes it compatible with several other shells.

- Don't use "function funcname()" syntax. It adds nothing over the basic "funcname()" syntax, but prevents your code running in many/most non-Bash shells. And consider putting your function-opening brace on a separate line (someone once told me that there are shells that won't accept any other function declaration style, but I've never seen one, so ymmv).

- Don't use "local" if you need to interoperate with ksh. Abandoning "local" pollutes global namespaces, though, so your call.

- Don't use substring expansion (e.g. extracting the 3rd-10th characters of a string via 'substr="${somevar:3:7}"'. That's not supported in many other shells. Alternatives include sed/awk/etc., or, if invoking external programs is absolutely unacceptable to you, something horrific like:

    substr()
    {
        local input="${1:?String is required}"  
        local dist_from_start="${2:?Start position is required}"
        local dist_from_end="${3:-${#input}}" # Here, it's actually 'offset', not distance.
        local start_nulls=
        local end_nulls=

        dist_from_end=$(( 5 * (${#input} - ($dist_from_start + $dist_from_end)) ))
        dist_from_start=$(( 5 * $dist_from_start ))

        # Make a string of the regex for "any not null character" that "masks" the
        # characters in the input before the start point, and the characters after
        # the end of the substring. This is disgusting, and is only done because the
        # parameter expansion statements can't contain repetitions (e.g. [^\0]{5})
        # without the bash-only 'extglob' shell option.
        # The not-null character is used because it will never match in a shell
        # string.
        while true; do
            if [ "${#start_nulls}" -lt $dist_from_start ]; then
                start_nulls="${start_nulls}[^\0]"
            elif [ "${#end_nulls}" -lt $dist_from_end ]; then
                end_nulls="${end_nulls}[^\0]"
            else
                break
            fi
        done

        input="${input#$start_nulls}"
        echo "${input%$end_nulls}"

    }

    substr "$@"
...actually, please never use that code. Ew.

Anyway, some more bashisms: https://mywiki.wooledge.org/Bashism

Re: Things I Wish I'd Known About Bash

#220
post #163

Earlier quoted context omitted.

As someone who also has to semi-frequently modify 100+ lines bash scripts written by others, I'd suggest every serious bash scripter read the bash man page. It is much smaller than any book on Python. For scripts written in Python, I'd use a similar argument and suggest every serious Python scripter to learn Python. As for Perl, or Ruby, or Julia, or anything really. It's just that learning bash from its man page is,…

i'd argue that it's much easier to write bad Bash than bad Python

Assuming that "bad" doesn't mean merely "ugly to glace at." I find that depends largely on the problem at hand.
Post reply on HN