Hacker News new | ask | show | jobs
by bsdetector 1327 days ago
Having written a lot of shell scripts, the single greatest thing I've ever experienced is shell-friendly outputs.

For example, consider if ls had a "--shell" option that output each entry as a single line of shell-quoted variables safe for eval:

    # cd /usr/share/dict
    # ls --shell words
    path="/usr/share/dict" file="words" user="root" group="root" size=985084 ...
Then all sorts of things become easy.

    # eval $(ls --shell words); echo "$size" "$file"
This nushell doesn't really change anything important about this biggest scripting problem. You're still parsing human readable output and it's still dependent on particular versions and options passed.

If I had a genie wish of nushell being installed everywhere or even just coreutils having a --shell option I'd take the latter any day.

8 comments

Calling an external too to list files is an anti-pattern; every programming language or shell language should have that built-in, so it's just doing that via the OS API (opendir/readdir/closedir).

The POSIX shell has that in the form of globbing. That only gives you names, and has quirks that are only adequately worked around with Bash options.

The fix is to use some real programming language for complex work.

I made a language for myself in this space, geared toward C and Unix people who are willing to try Lisp: TXR.

  1> (stat "/usr/share/dict/words")
  #S(stat dev 2306 ino 884986 mode 33188 nlink 1 uid 0 gid 0 rdev 0 size 931708
          blksize 4096 blocks 1832 atime 1667315034 atime-nsec 0 mtime 1238396423
          mtime-nsec 0 ctime 1405615444 ctime-nsec 0 path "/usr/share/dict/words")
  2> (flow "/usr/share/dict/words"
            stat
            [callf list .path .size])
  ("/usr/share/dict/words" 931708)
  3> (flow "/usr/share/dict/words"
            stat
            [callf list .path .size [chain .uid getpwuid .name]])
  ("/usr/share/dict/words" 931708 "root")
> Having written a lot of shell scripts, the single greatest thing I've ever experienced is shell-friendly outputs.

This is basically the idea of PowerShell. Output is well formatted data that can be passed around and manipulated in a regular manner. No slicing on columns or anything like that.

PowerShell has other issues (many of which are documented in these very comments!) but shell friendly output is one of its central ideas.

Well in nushell you wouldn't be using the coreutils commands, you'd be using the nushell builtin replacements. Those builtins return structured data.

Now for the 1000 other non-coreutils commands on your system, that's not very helpful. --shell is a good idea, though it seems like JSON is becoming the most common alternative structured output implemented by commands. Nushell and PowerShell can turn JSON into a structured variable easily, though using jq in shell is alright most of the time.

jc [0] is dedicated to providing this externally for common commands, but I agree that it would be better if command authors built it in as an option. It would make shell scripts shorter, more readable, and less buggy.

[0] https://github.com/kellyjonbrazil/jc

I like the idea, but if it was interpreted directly, that would be a security nightmare unless we also had something like Python's ast.literal_eval(). Which makes us come back to JSON-like outputs because we need some form of serialization anyway, I guess.
Of course there could be some tweaks like a scoped eval to prevent stomping on the script's variables, but it's really not very hard for a C program to escape shell variables correctly and safely.

You could have an eval that only read variables, but trusting a program to only return variables is a really low bar; it's very hard to mess that up.

Part of it is knowing the tools. In this case, 'stat' is the command you probably want to get parsable details about a file, not more options to 'ls'. I often see convoluted shell scripts that could be a lot simpler with the use of more appropriate utilities.
It's just an example. With shell-friendly outputs you can recreate "ls -l" with any field order you want with just a few lines of readable shell script. Many of the unix tools only exist because it's so hard to parse the human-readable output in the shells.
Wouldn't 'find .. -exec ' and friends get you this?

    find . -type f -depth 1 -exec ls -la {} + | awk {'print "file="$9 " user=" $3 " group=" $4 " size=" $5'}
Your awk has to put out file="..." notation, and if double quotes occur, they have to be escaped. So you're looking at substantially longer command.

  | awk 'function esc(str) {
         ...
         }
         { print "file=\"" esc($9) " user=\"" ... }'
Many tools have some switch which will make them output JSON, which is easy to process if you have jq. For example `docker something --format='{{json .}}'`.