16 comments

[ 4.6 ms ] story [ 47.4 ms ] thread
Hats off to the author! I've long been a proponent of the colon command due to its no-op properties, but this just takes it to another level. One would definitely have to provide add comments to the ":" function as there seems to a good amount of esoterica contained within.
I see 2 problems here:

major: it does not work on POSIX shells, because ':' is not a valid function name and

minor: the function itself should be changed to use the builtin 'case' instead of the bashims [[ ... ]]

Hey, thanks for taking the time to read and commenting! :)

Problem #1 (at least for the "advanced" part of the article's suggestion) is unfixable unless you boil the ocean and change the POSIX spec. I tried to make that clear in the prose, but might have failed at it. It's also the reason why I chose the title to refer to `bash` scripts in particular.

Problem #2 becomes less relevant once you realize that problem #1 completely overshadows it. And in `zsh`, where you can also redefine `:` to invoke a custom function, tests via `[[` also work just like they do in bash.

what seems possible is:

  #!/bin/sh
  
  alias :=mydebug
  mydebug() { printf '%s\n' "$\*"; }
  
  foo=1
  : this is a debugable comment
  bar=2
Interesting find! Apparently, also in POSIX sh programming, all problems can be overcome by introducing another layer of indirection ;)

Would it be OK for you if I added that information/workaround to the article (crediting you, of course)?

How is the POSIX non compliance a major problem? Does it have practical impacts when writing scripts that are not meant to be portable to other systems/shells?

  "Stick to portable constructs where possible, and
   you will make somebody's life easier in the future.
   Maybe your own."
Clever work, thank you for sharing!

Here's how I do the equivalent with a POSIX function to print a message to STDOUT:

    out() { printf %s\\n "$*" }
Brilliant! The most personally useful thing I have seen on HN in ages. And very nicely written too.
That's a nice article.

It prompted me to mention BashSupport Pro which I installed a couple of months back.

It's a plugin for JetBrains IDEs that allows step debugging and variable inspection for Bash scripts in the same way as other supported languages.

https://www.bashsupport.com/

Unfortunately it doesn't work if you forbid unbound variables with set -u. Like this (as suggested in the Unofficial strict mode article: https://news.ycombinator.com/item?id=8054440):

  $cat z
  #!/bin/bash

  set -u # fail if unset variable is used

  if [[ -n ${COLODEBUG} && ${-} != *x* ]]
  then
      :() {
          [[ ${1:--} != ::* ]] && return 0
          printf '%s\n' "${*}" >&2
      }
  fi
  
  echo hello
  $./z
  ./z: line 10: COLODEBUG: unbound variable
  $
The -n test should look like this:

  if [[ -n ${COLODEBUG:-} && ${-} != *x* ]]
You are right, this change makes the drop-in snippet more robust: If one issues `set -u` before trying or deciding whether to redefine `:` (which would also be the effect of invoking the interpreter with the `-u` option), the variant presented in the article breaks. If the shall-we-redefine-: check has already been performed before `-u` is in effect, the alternative implementation of `:` itself will work fine.

It's kinda unfortunate this slipped through, because before I tried to "formalize" this little trick in the article yesterday, I definitely remember having used variants that guarded against this little nasty trap you now independently rediscovered ;)

Would you mind me updating the article accordingly, and crediting your HN account name/profile in the changelog?

Thanks a lot for your rigorous reading and very constructive feedback! :)

> Would you mind me updating the article accordingly, and crediting your HN account name/profile in the changelog?

No problem. I'm retired now so my name doesn't appear in changelogs as often as it used to!

I have another couple of suggestions.

The first suggestion is to add

     export -f :
just before the fi so that it is available to scripts called from the main script.

The second is to add

  local IFS=" " # Insulate : from changes to IFS
before the printf.

because if I didn't then each word of the comment would appear on a separate line like this:

  ::
  a
  b
  c
instead of this:

  :: a b c
the reason for this is that I had defined

  IFS="/n/t"
as suggested by the Unofficial Strict Mode.
So basically it's a "_debug" function using the ":" function name and double bracket syntax