1. 67
  1.  

  2. 10

    Neat trick!

    I think the T is not needed: you can use sed -n 's/^### \?//p'. A command right after the s///, with no semicolon, only runs if the s/// replaced something.

    1. 9

      Ah this is a neat idea! Note that the T command only works for GNU sed; just something to be aware of as it may matter in some situations. You can probably make a POSIX version as well (but I can’t be bothered right now to try and come up with something 😅)

      1. 6

        This is nice.

        Being old skool, I would routinely start my bash scripts with a usage() function (containing a bunch of echo statements amounting to the same thing) that would be called by any failure in the getopts processing but this a simple alternative that is useful both for the user and for a casual reader of the script.

        1. 2

          Yeah, this is a really clever way to make your code more readable and make it easier to get help.

        2. 4

          Not quite the same, but it reminds me of a neat trick I though about when making cheatsheets. For example, I got a nato script that prints the NATO alphabet that goes like this:

          #!/bin/cat
          A - Alfa         N - November
          B - Bravo        O - Oscar
          C - Charlie      P - Papa
          D - Delta        Q - Quebec
          E - Echo         R - Romeo
          F - Foxtrot      S - Sierra
          G - Golf         T - Tango
          H - Hotel        U - Uniform
          I - India        V - Victor
          J - Juliett      W - Whiskey
          K - Kilo         X - X-ray
          L - Lima         Y - Yankee
          M - Mike​         Z - Zulu
          

          The trick here is to use cat as the “script interpreter” (or less, more, whatever…).

          1. 2

            Or without the first line:

            #!/usr/bin/env tail -n+2
            A - Alfa         N - November
            B - Bravo        O - Oscar
            C - Charlie      P - Papa
            D - Delta        Q - Quebec
            E - Echo         R - Romeo
            F - Foxtrot      S - Sierra
            G - Golf         T - Tango
            H - Hotel        U - Uniform
            I - India        V - Victor
            J - Juliett      W - Whiskey
            K - Kilo         X - X-ray
            L - Lima         Y - Yankee
            M - Mike         Z - Zulu
            
          2. 3

            Another method that I like is a HELP variable at the start of script which you can then reuse for usage output:

            #!/usr/bin/env bash
            HELP=$(
            	cat <<-EOF
            		This is a git prepare-commit-msg hook which queries tmux for a list of
            		attached users and adds them as co-authors based on their email addresses found
            		in ldap. It is called by git with 1-3 params: commit_file_path, commit_source &
            		commit_old_sha, see githooks(5) for more info. This script also installs and
            		uninstalls the hook, -i & -u.
            	EOF
            )
            
            1. 3

              What I really want is config free autocomplete for shell scripts. Too often do I sit hitting tab and wonder why nothing of value happens.

              1. 2

                Permit me to connect your comment to @duncaen’s

              2. 2

                I do something similar with normal comments at the beginning of the script and usually not as help message but with a separate command called “twoman” because adding option parsing to most scripts doesn’t really make sense.

                https://xn--1xa.duncano.de/twoman.html

                1. 1

                  Permit me to connect your comment to @adventureloop’s

                2. 2

                  Solid

                  1. 2

                    I’ve done similar embedding, but with perldoc rather than sed. The shell doesn’t natively ignore POD directives but you can use : (no-op) and a heredoc:

                    #!/bin/sh
                    set -eu
                    
                    case ${1:-} in *help|-h)
                      exec perldoc -T $0;;
                    esac
                    
                    usual script body
                    
                    :<<=cut
                    =head1 NAME
                    
                    some script
                    
                    =head1 SYNOPSIS
                    
                    ...
                    
                    =cut
                    
                    1. 2

                      Here’s another variation on shell scripts presenting help information:

                      ARGUMENTS+=(
                        "a,arch,Target operating system architecture (amd64)"
                        "b,build,Suppress building application"
                        "o,os,Target operating system (linux, windows, mac)"
                        "u,update,Java update version number (${ARG_JRE_UPDATE})"
                        "v,version,Full Java version (${ARG_JRE_VERSION})"
                      )
                      

                      The lines are machine-readable and alignment is computed by a bash build script template:

                      https://github.com/DaveJarvis/scrivenvar/blob/master/build-template#L186

                      When the installer script’s help is requested, the following is produced:

                      $ ./installer -h
                      Usage: installer [OPTIONS...]
                      
                        -V, --verbose  Log messages while processing
                        -h, --help     Show this help message then exit
                        -a, --arch     Target operating system architecture (amd64)
                        -b, --build    Suppress building application
                        -o, --os       Target operating system (linux, windows, mac)
                        -u, --update   Java update version number (8)
                        -v, --version  Full Java version (14.0.1)
                      

                      Using an array reduces some duplication, though more can be eliminated. Scripts typically have two places where the arguments are referenced: help and switch statements. The switch statements resemble:

                      https://github.com/DaveJarvis/scrivenvar/blob/master/installer#L191

                      Quite often argument parsing entails either assigning a variable or (not) performing an action later. Introducing another convention would allow hoisting the switch statement out of the installer script, up into the template. Off the cuff, this could resemble:

                      ARGUMENTS+=(
                        "ARG_ARCH,a,arch,Target operating system architecture (amd64)"
                        "do_build=noop,b,build,Suppress building application"
                        "ARG_JRE_OS,o,os,Target operating system (linux, windows, mac)"
                        "ARG_JRE_UPDATE,u,update,Java update version number (${ARG_JRE_UPDATE})"
                        "ARG_JRE_VERSION,v,version,Full Java version (${ARG_JRE_VERSION})"
                      )
                      

                      The instructions to execute when arguments are parsed are thus associated with the arguments themselves, in a quasi-FP style. This approach, not including the FP convention, is discussed at length in my Typesetting Markdown series.

                      1. 2

                        Neat!

                        I believe scoop does something very similar (or used to, stuff might have changed since I last dug into its source).

                        1. 1

                          That is both a practical and cute bit of shell metaprogramming. I am also guilty of using sed in a bit of shell metaprogramming, for some directory bookmarks.

                          1. 1

                            I like using the mask tool (https://github.com/jakedeichert/mask) to sort of flip this and put usage docs and argument descriptions front-and-center, with the result being almost literate coding feel for shell scripts. The implementation can still just be a bunch of bash, you just don’t have to try to shove all your docs in a comment and rely on roll-your-own arg handling.

                            1. 1

                              It might be useful to replace "$0" with "$(which "$0")", so that if you’re executing the script from the PATH it will still get the help correctly.

                              1. 4

                                $0 is the full command path, even if it’s executed from PATH. At least it is on my system under Dash 0.5.10.2 and Bash 5.0.17.

                                1. 2

                                  Also the case on OpenBSD ksh.

                                2. 1

                                  Another possibility is to create some constants:

                                  readonly SCRIPT_SRC="$(dirname "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}")"
                                  readonly SCRIPT_DIR="$(cd "${SCRIPT_SRC}" >/dev/null 2>&1 && pwd)"
                                  readonly SCRIPT_NAME=$(basename "$0")
                                  
                                3. 1

                                  The last time I had to work on a large shell project, I started out doing something similar to this, but found it a little limited, so I wrote this. This is more focused on projects big enough to have a wiki, and libraries or shell functions. The approach presented in the article works great for small standalone scripts.

                                  1. 2

                                    Yep - I took a pretty similar approach, for pretty similar reasons. The biggest difference is that I targeted markdown rather than RST, and I wrote mine in shell.

                                  2. 0

                                    Excellent!