Live data from Hacker News

Techniques I use to create a great user experience for shell scripts

nochlin.com

61–70 of 281 posts

Re: Techniques I use to create a great user experience for shell scripts

#61
Few months ago, I wrote a bash script for an open-source project.

I created a small awk util that I used throughout the script to style the output. I found it very convenient. I wonder if something similar already exists.

Some screenshots in the PR: https://github.com/ricomariani/CG-SQL-author/pull/18

Let me know guys if you like it. Any comments appreciated.

    function theme() {
        ! $IS_TTY && cat || awk '

    /^([[:space:]]*)SUCCESS:/   { sub("SUCCESS:", " \033[1;32m&"); print; printf "\033[0m"; next }
    /^([[:space:]]*)ERROR:/     { sub("ERROR:", " \033[1;31m&"); print; printf "\033[0m"; next }

    /^        / { print; next }
    /^    /     { print "\033[1m" $0 "\033[0m"; next }
    /^./        { print "\033[4m" $0 "\033[0m"; next }
                { print }

    END { printf "\033[0;0m" }'
    }
Go to source: https://github.com/ricomariani/CG-SQL-author/blob/main/playg...

Example usage:

    exit_with_help_message() {
        local exit_code=$1

        cat 
Go to source: https://github.com/ricomariani/CG-SQL-author/blob/main/playg...

        cat 
Go to source: https://github.com/ricomariani/CG-SQL-author/blob/main/playg...

Re: Techniques I use to create a great user experience for shell scripts

#63
post #41

These are all about passive experiences (which are great don't get me wrong!), but I think you can do better. It's the same phenomenon DHH talked about in the Rails doctrine when he said to "Optimize for programmer happiness". The python excerpt is my favorite example: ``` $ irb irb(main):001:0> exit $ irb irb(main):001:0> quit $ python >>> exit Use exit() or Ctrl-D (i.e. EOF) to exit ``` Ruby accepts both exit and q…

Yes. I'd be surprised if exit without parentheses quit the interactive shell when it doesn't quit a normal python script.

Ipython quits without parenthesis.

Re: Techniques I use to create a great user experience for shell scripts

#64
post #41

These are all about passive experiences (which are great don't get me wrong!), but I think you can do better. It's the same phenomenon DHH talked about in the Rails doctrine when he said to "Optimize for programmer happiness". The python excerpt is my favorite example: ``` $ irb irb(main):001:0> exit $ irb irb(main):001:0> quit $ python >>> exit Use exit() or Ctrl-D (i.e. EOF) to exit ``` Ruby accepts both exit and q…

On the one hand, being generous in your inputs is always appreciated. On the other hand, the fact that both exit and quit will terminate ruby means the answer to "how do I quit ruby" now has two answers (technically 4 because `quit()` and `exit()` also work, and if we're talking about "least surprise" if you accept "exit" and "quit", why not also "bye" or "leave" or "close" or "end" or "terminate".

Python might be surprising, but in this example, it's only surprising once, and helpful when it surprises you. Now you know quitting requires calling a function and that function is named exit() (although amusingly python3 anyway also accepts quit()). And being fully pedantic it doesn't know what you mean, it is assuming what you mean and making a suggestion, but that's not the same as knowing.

From here on I'm not arguing the point anymore, just recording some of the interesting things I discovered exploring this in response to your comment:

You can do this in python (which IMO is surprising, but in a different way):

  ```
  >>> quit
  Use quit() or Ctrl-D (i.e. EOF) to exit
  >>> quit=True
  >>> quit
  True
  >>> quit()
  Traceback (most recent call last):
    File "", line 1, in 
  TypeError: 'bool' object is not callable
  >>> exit()
  ```
But this also gives some sense to python's behavior. `quit` and `exit` are symbol names, and they have default assignments, but they're re-assignable like any other symbol in python. So the behavior it exhibits makes sense if we assume that they're not special objects beyond just being built int.

`exit` is a class isntance according to type. So we should be able to create something similar, and indeed we can:

  ```
  >>> class Bar:
  ...   def __repr__(self):
  ...     return "Type bar() to quit!"
  ...   def __call__(self):
  ...     print("I quit!")
  ...
  >>> bar = Bar()
  >>> bar
  Type bar() to quit!
  >>> bar()
  I quit!
  >>>
  ```
Interestingly this suggests we should be able to replace exit with our own implementation that does what ruby does if we really wanted too:

  ```
  >>> class SuperExit:
  ...   def __init__(self, real):
  ...     self.real_exit=real
  ...   def __repr__(self):
  ...     print("Exiting via repr")
  ...     self.real_exit()
  ...   def __call__(self):
  ...     print("Exiting via call")
  ...     self.real_exit()
  ...
  >>> exit = SuperExit(exit)
  >>> exit
  Exiting via repr
  ```

Re: Techniques I use to create a great user experience for shell scripts

#65
It is impossible to write a safe shell script that does automatic error checking while using the features the language claims are available to you.

Here’s a script that uses real language things like a function and error checking, but which also prints “oh no”:

  set -e

  f() {
    false
    echo oh
  }

  if f
  then
    echo no
  fi
set -e is off when your function is called as a predicate. That’s such a letdown from expected- to actual-behavior that I threw it in the bin as a programming language. The only remedy is for each function to be its own script. Great!

In terms of sh enlightenment, one of the steps before getting to the above is realizing that every time you use “;” you are using a technique to jam a multi-line expression onto a single line. It starts to feel incongruous to mix single line and multi line syntax:

  # weird
  if foo; then
    bar
  fi

  # ahah
  if foo
  then
    bar
  fi
Writing long scripts without semicolons felt refreshing, like I was using the syntax in the way that nature intended.

Shell scripting has its place. Command invocation with sh along with C functions is the de-facto API in Linux. Shell scripts need to fail fast and hard though and leave it up to the caller (either a different language, or another shell script) to figure out how to handle errors.

Re: Techniques I use to create a great user experience for shell scripts

#66
post #41

These are all about passive experiences (which are great don't get me wrong!), but I think you can do better. It's the same phenomenon DHH talked about in the Rails doctrine when he said to "Optimize for programmer happiness". The python excerpt is my favorite example: ``` $ irb irb(main):001:0> exit $ irb irb(main):001:0> quit $ python >>> exit Use exit() or Ctrl-D (i.e. EOF) to exit ``` Ruby accepts both exit and q…

this actually completely turned me off from python when I first encountered it. I was like... "the program KNEW WHAT I WAS TRYING TO DO, and instead of just DOING that it ADMONISHED me, fuck Python" LOL The proliferation of Python has only made my feelings worse. Try running a 6 month old Python project that you haven't touched and see if it still runs. /eyeroll

>Try running a 6 month old Python project that you haven't touched and see if it still runs.

My experience has been 6 month of python works fine. In fact, python is my go to these days for anything longer than a 5 line shell script (mostly because argparse is builtin now). On the other hand, running a newly written python script with a 6 month old version of python, that's likely to get you into trouble.

Re: Techniques I use to create a great user experience for shell scripts

#67

Not trying to offend anyone here but I think shell scripts are the wrong solution for anything over ~50 lines of code. Use a better programming language. Go, Typescript, Rust, Python, and even Perl come to mind.

> shell scripts are the wrong solution for anything over ~50 lines of code. I don't think LOC is the correct criterion. I do solve many problems with bash and I enjoy the simplicity of shell coding. I even have long bash scripts. But I do agree that shell scripting is the right solution only if = you can solve the problem quickly = you don't need data structures = you don't need math = you don't need concurrency

~1k lines of bash with recutils for data persistency and dc for simple math. Was not quick to solve for me, custom invoices .fodt to .pdf to email, but I got it done. Shell is the only other scripting language I am familiar with other than Ruby. And I am worse at Ruby.

Sometimes options are limited to what you know already.

Re: Techniques I use to create a great user experience for shell scripts

#68
post #18

Don't output ANSI colour codes directly - your output could redirect to a file, or perhaps the user simply prefers no colour. Use tput instead, and add a little snippet like this to the top of your script: command -v tput &>/dev/null && [ -t 1 ] && [ -z "${NO_COLOR:-}" ] || tput() { true; } This checks that the tput command exists (using the bash 'command' builtin rather than which(1) - surprisingly, which can't alwa…

Yes and even better for faster speed and greater shell compatibility for basic colors, you can use this POSIX code:

    if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
      COLOR_RESET=''
      COLOR_RED=''
      COLOR_GREEN=''
      COLOR_BLUE=''
    else
      COLOR_RESET=''
      COLOR_RED=''
      COLOR_GREEN=''
      COLOR_BLUE=''
    fi
For more about this see Unix Shell Script Tactics: https://github.com/SixArm/unix-shell-script-tactics/tree/mai...

Be aware there's an escape character at the start of each of color string, which is the POSIX equivalent of $'\e'; Hacker News seems to cut out that escape character.

Re: Techniques I use to create a great user experience for shell scripts

#69
post #66

Earlier quoted context omitted.

this actually completely turned me off from python when I first encountered it. I was like... "the program KNEW WHAT I WAS TRYING TO DO, and instead of just DOING that it ADMONISHED me, fuck Python" LOL The proliferation of Python has only made my feelings worse. Try running a 6 month old Python project that you haven't touched and see if it still runs. /eyeroll

>Try running a 6 month old Python project that you haven't touched and see if it still runs. My experience has been 6 month of python works fine. In fact, python is my go to these days for anything longer than a 5 line shell script (mostly because argparse is builtin now). On the other hand, running a newly written python script with a 6 month old version of python, that's likely to get you into trouble.

argparse? docopt or google-python-fire

Re: Techniques I use to create a great user experience for shell scripts

#70
Here's a script that left an impression on me the first time I saw it:

https://github.com/containerd/nerdctl/blob/main/extras/rootl...

I have since copied this pattern for many scripts: logging functions, grouping all global vars and constants at the top and creating subcommands using shift.

Post reply on HN