Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • life is way too short to deal with this nightmare of a language and its 50000 footguns for anything longer than a 2 line script, especially in the age of LLMs. Just write a python/TS/any real language script instead. Bash is great for the command line, it should be limited to use there.
  • > 50000 footguns

    (...)

    > especially in the age of LLMs

    Funny way of putting it.

    But I think you are right still :)

  • Just use Fish
  • What other language is it as easy, fast, and painless to work with file contents? I can toss data down and lift it back up with some <> symbols and a path, none of Python's "errrm, how would you like to open that file " handholding. And why the hell would I want to go learn TypeScript!?!?

    Sometimes I feel like the people on HN repeating the trope "uhhh if you need ____ go learn a real language!" are just bad at writing shell scripts.

    by wpm
  • Python startup time is a problem. I don't know about ts but I bet it's worse.
  • I find LLM's too fall into the trap of writing a bash script for a task that clearly needs to be implemented in an Actual Language with Real Data Structures. For example, ask an LLM to bring up a SQL server with some schema + data preload step, and it will write a profoundly long bash script to do that task, every time.
  • It turns out that LLMs are really good at writing bash too. even perl! maybe we should rethink some of these lost bits because we no longer need to worry about the arcane parts.
    by jghn
  • Nice one! I love those weird bash tricks.

    Some of the examples here are interesting, but they show parameter substitution more than colon itself: https://tldp.org/LDP/abs/html/parameter-substitution.html

    In small scopes, I tend to inline the `:?` validation inside the arg of the command. `echo "${1:? first param required}"`

    Another usecase is to use colon in the body of a while loop, while doing work in the condition of the loop.

        while rlwrap -o -S'>> ' tr a-z A-Z ; do :; done
    
    Gives you the "do X while it succeeds. stop when it returns non-0" semantics.

    I've also written about this and other bash tricks over the years in https://github.com/kidd/scripting-field-guide/blob/master/bo.... You might like them :)

  • I hate weird bash tricks with a passion.

    The extent to which the script author gets to feel smart and efficient is exactly the extent to which the future reader of the script gets to feel like an idiot.

  • This "hidden knowledge" is fun to read but pain to remember and use, especially when working with multi-platform environments //

    Switched from bash to plain python scripts for shell stuff everywhere several years ago, and never looked back into bash zoo anymore. Stable syntax across Win/Mac/Linux, no bash/zsh/msys2 obscure differences, normal errors and Clause writes scaffolds quick and flawless anyway

  • I find Python a terrible bash replacement.

    Calling all the command line tools is cumbersome, the Python cloud APIs are verbose compared to the cli tools and often have very different concepts for how they interact with services.

    But I agree that there is a need for a better bash and zsh is nice but not the upgrade that is needed

  • I guess if you're never calling out to any third-party python libraries, that can work.

    I'd never call Python "portable" though if you have outside dependencies.

    by wpm
  • Off-topic, but I am reminded of Larry's First and Second Laws of Language Redesign (which Larry Wall discovered/stated when he designed Perl 6, which is now Raku):

        1. Everyone wants the colon.
        2. Larry gets the colon.
  • Why take a perfectly readable if-statement and turn it into something, 99.9% of people would need to lookup. Concise != better. You can make it one line with:

        [ -z "$1" ] && { echo "missing argument, aborting." 1>&2; exit 1 }
  • I believe you missed a semicolon after `exit 1` and before `}`. `}` closes the opening `{` only at the start of a command (POSIX rules; don't remember now if bash recognizes it when it's just a command argument).
    by tzot
  • the people writing it are doing it for themselves probably. i wouldn’t expect it to survive a code review. the necessity of using esoteric bash features or syntax is a pretty good smell that you should be using something else imo.

    different strokes of course.

  • This question seems to pop up all over the place, so I took some time to answer it here:

    https://refp.se/articles/your-shell-and-the-magic-colon#why-...

    by refp
  • My personal favourite use of the colon command is what I've written about some years ago: https://johannes.truschnigg.info/writing/2021-12_colodebug/
    by c0l0
  • I actually did absolutely need it recently when I golfed together a shell script that is simultaneously a valid YAML file [0]. Sometimes having no-op tokens is nice!

    [0]: https://domi.work/blog/posts/compose_polyglot/

  • I use the colon as EDITOR with Git when I want to do an interactive rebase combined with auto squash without having to edit the todo list.

    I have an alias[1] for that which I call a quick interactive rebase:

        riq = -c sequence.editor=: rebase --interactive
    
    [1]: https://github.com/fphilipe/dotfiles/blob/94f2ff70bade070694...
  • nice dotfile
  • damn, nice one — definitely gonna use this myself (or variations thereof)!
    by refp
  • That seems unsafe. It assumes that the editor is started via a shell command, and not executed directly. But there is no actual binary /usr/bin/: but there is a /usr/bin/true; I would use that instead.
  • I once created a set of shell functions which I wanted to have a docstring-like functionality. The solution I came up with was to have each shell function start with the : command with a string as an argument. Since : is an actual command, not a comment, it was preserved as part of the function, and could be extracted at runtime using relatively simple parsing to do introspection.

    Example:

      foo(){
        : "This is a docstring for the foo() function"
        bar --verbose | baz --quiet
      }
    
    
    (Repost of <https://news.ycombinator.com/item?id=29152308>)
  • I am not a huge fan of most of these, but a few do seem useful.

        : "${1:?missing argument, aborting!}"
    
    I wouldn't use this because I would want to give $1 a name for the rest of the script, so I would assign. But it can be a nice way to give a clear error for missing required environment variables.

    Many of the others (like truncating files) are probably more clearly written with dedicated commands, but may come in useful if you are going to extreme lengths to avoid dependencies outside of the shell.

  • I use this for setting overridable defaults (this should hopefully actually be in the article I haven't read yet)

    : ${DEBUG:=false}

    debug is not only false, but garanteed to be set to something so that elsewhere the places that use it don't need extra syntax and checking in case it's empty, and yet you can just set it from the parent environment to override without touching the script or adding a commandline args parser.

    And do me, reading this is almost as easy on the brain as just plain DEBUG=true.

    Even if you aren't familiar with the extra syntax, like you're someone else in the future who needs to look at it, the two important words pop out, and probably don't mean NOT because whatever those extra bits mean, there's no !. So you get the idea well enough & cruise on.

    ...[edit] yep it's in the article. but one thing isn't, exactly

        condition && {
            then-cmd   # might exit > 0
            :          # ensure this block ends true
        } || {
            else-cmd
        }
    
    In other words, maybe you should have figured out some other way to write the whole construct, but IF you want to write the if/else this way maybe for just readability or organization, and you want the else-block to only hinge on the initial condition and not also on whatever the then-block might do, then you need a safety-true at the end of the then-block.
  • "Shortcuts" like :? that tie together two unrelated things (checking a variable for a condition, expanding the value of that variable) drive me up the wall. It makes expressing just one of the two things harder than expressing both, leading to contortions when you want just one, like this use of :.
  • $1 might also just be the first parameter for a function call, so you might not need that parameter to have a specific/readable name if the function is not very complex or long. I have plenty in my dot-files that are just one-line functions that don't need to be three-line functions just to give the single parameter it accepts a name.
    by wpm
  • Agreed; author does note

    > Why use the null-command when I could do VAR=${VAR:-default-value}?

    and points out it's one less thing to typo, but that assumes the name is the same; I like e.g.

      TARGETFILE="${1:?need input file}"
      OPTIONALVAL="${2:-defaultvalue}"
  • Yo, author here.

    You are very much correct and I 100% agree with you, I have updated the first example to include a snippet where a proper env-var is used to show of the automatic diagnostic.

    Thanks for your feedback, much much appreciated!

    by refp