Skip to content

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 24 Oct 00:56
· 746 commits to main since this release

Strings that a command runs as a script and bats test files are explained, conditions that the script's own values decide say which code never runs, and worked examples read and print as the tools do.

New

  • A string that a command runs as a script is explained as one: bash -c '…', sh -c, eval, a trap's action, su -c, ssh host '…' and watch '…', also under sudo, xargs, find -exec, docker exec and kubectl exec. Its commands and expansions appear at their own characters, its notes are those of the shell that runs it (an sh -c string gets the POSIX checks), and the Problems list shows its dialect issues. What a static eval string or a trap's action sets counts after it. A string with an expansion (eval "$cmd") says its text is known only when it runs. Before, such a string was one opaque argument.
  • New notes on conditions that the values the script set decide: "$mode is fast here, so this condition is always false: the commands after its then never run", or "so only fast) of this case runs". Expansions such as ${x:-default}, ${x#prefix} and $(( )) are read as bash expands them. Conditions of literals only (while true, if false) say nothing more.
  • bats test files (.bats, or a bats shebang) are explained: @test "…" {, run, load, skip and the other bats commands, with pitfalls for a ! cmd that cannot fail a test and for run in a pipeline. VS Code, the Language Server, the command-line tool (--kind bats) and the web page (with a bats sample) recognise them. Before, @test was a syntax error and run an unknown command.
  • env -S is read as GNU env reads it. A shebang #!/usr/bin/env -S bash -e runs bash -e with the script. #!/usr/bin/env bash -e warns that Linux passes bash -e to env as one word, which fails. An env -S string is split as GNU env splits it (quotes, \_, # comments, ${NAME}), and a string env refuses gets a warning that it exits with status 125.
  • ksh93, mksh, lksh, pdksh, oksh, posh, yash and ash (also by path) take the options of sh: mksh -c '…' runs the string as a script. Before, such a name was an unknown command.
  • A note says when a command does not read the standard input the line gives it, because it reads the files it names: jq . a.json < b.json reads only a.json.
  • The command-line tool takes --sarif <file> with --check: it also writes the problems to that file as SARIF. The GitHub Action uses it, so it checks the scripts once for both the annotations and the SARIF file.

Changed

  • VS Code settings: a setting set below its minimum (bashExplainer.inline.maxLength under 20, bashExplainer.hover.maxDepth or hover.followDelay under 0) now uses its default, as the command-line tool already did. Before, VS Code used the number as given.
  • A branch that cannot run, because the script's own values decide its condition, no longer changes how later lines are explained: after x=1; if [ "$x" = 2 ]; then set -f; fi, a glob is a glob again. Code after break, continue, return or exit no longer counts either. Before, both sides of every branch counted, so such lines said "may" or "on some paths". In a script that runs code that cannot be read (source, eval "$s", $cmd, ${!v}), both sides still count.
  • The worked examples' sections say what the command reads and where from: "Input: a.json (sample contents)", "Input: stdin (sample output of the previous command)", "Input: files matching *.json". A section that is not read is no longer called input: date +%F says "When it runs (made up, UTC)". Before, every such section was called "Input" (or "Date and time (UTC)").
  • The colours of the parts of a line (command, option, variable…) are readable as text: light themes use darker colours of the same hues (at least 4.5:1), high contrast themes their own (7:1), and errors a red that passes too. The web page has the same colours. Before, they were VS Code's chart colours, as low as 2.9:1 on white.
  • On the web page, changing the file kind keeps the text, also an untouched sample. "Load the sample" loads the current kind's sample, with an "Undo" button when it replaces your own text. Before, choosing a kind replaced an untouched sample, and "Load the sample" could be undone only with Ctrl+Z.

Fixed

  • Worked examples print the output as the tools print it. jq uses two spaces a level (one line with -c; --tab, --indent n and -S are followed), and reads -R, input and inputs. mikefarah/yq prints JSON as yq does, reads a .json file as JSON, prints strings bare with -r, and shows with -i what the first file becomes. awk gives FILENAME the name of each file. sort -o and uniq in out show what is written to the file. Before, jq and yq printed every result on one line, and FILENAME was empty.
  • Worked examples read what the command really reads. A here-string, a here-document or a pipe of known text is the input itself (echo '{"a":1}' | jq .a). jq -n and an awk program of BEGIN rules only show no input. Several files (sort a b, head -n 1 a b) show a section each. cat and nawk get real examples. Before, the example made up one input for all.
  • Worked examples follow the command's options. grep -e -v foo searches for -v, sed -ie 's/a/b/' -e 's/c/d/' f runs only s/c/d/, and find … -exec cmd {} '+' runs the command once. A line the tool refuses (grep -E -F, tar -c -x, seq 1 -w 3) says so and gets no example. A function or alias of the script named like a program (sort() { tac; }) no longer shows that program's output. Before, these examples showed what the tool does not do.
  • Options are read as each tool reads them. GNU tools (grep, sed, coreutils, diff, gzip, tar) accept a long option's unique prefix (ls --almost is --almost-all) and know every long option of their --help. test reads no options, xargs --max-lines echo runs echo, perl -0ne is -0 -n -e, and bash -O extglob -c '…' and bash --posix are known. Global options are told apart from a subcommand's (docker -H h run, kubectl -o yaml get pods). Before, many of these were "not in the dictionary" or taken for values.
  • Explanations match bash in more places: x=1 '' does not set x, exec {fd}>file, wait -p var and coproc name set their variables, and a division by 0 in $(( )) discards the rest of the line. unset f removes the function when there is no variable f. exec cmd in ( ) ends only the subshell. cmd &> >(tee log) sends both outputs to the process substitution; before, it was read as an argument, or as a syntax error after a block or a loop. In the body of for (( i = 0; … )), $_ is no longer said to be empty.
  • [, unset and declarations are explained as what bash runs, however they are written: \[ -f x ], command [ -f x ], x=1 unset x, \declare a=$v. [ -f x with no ], and words test cannot read ([ a b c d ]), say that the command fails with status 2. unset 'a[1]' removes an element, and an unquoted a[1] is a glob first. Where aliases are expanded, an alias named like a builtin runs the alias. Before, these forms were "not in the dictionary" or explained as working, and unset of a read-only variable said it removed it.
  • Redirections are read wherever they are written: before the command's name, among its words, and between assignments (a=1 >f b=2). A command of only redirections and assignments has the status of the last $( ) in its redirections, so x=1 >f$(false) fails. A pipeline stage that sends its output to a file (>out grep x in | sort) gets no example of the whole pipeline. Before, a line such as a=1 >f b=2 left the whole script unexplained, and such commands had the status 0.
  • The ⚠ notes follow what bash does here. After IFS=; set -f, cp $a b no longer says $a is split. A variable set by printf -v or mapfile at the end of a pipeline is reported as lost. The pitfall for a variable another shell does not have sees \$n in bash -c "echo \$n", and knows that env -u x removes x. egrep -q counts as grep -q, and an alias of rm as rm.
  • The "Idiom" notes are read from the commands, not from the text, so other spacing, order and quoting are found (while true ; do, done <f, trap cleanup 0). They are no longer given where bash does something else: trap - EXIT sets no clean-up, while [ $# > 0 ] writes to a file named 0, and text in quotes is not run.
  • The shell a script runs in is read in more places. In a GitHub Actions workflow, shell: counts above run:, and defaults: or runs-on: count after the steps. A Dockerfile or Makefile SHELL and a Python executable= are read as the same shell everywhere: SHELL ["/bin/dash","-c"] is dash. Busybox shells (RUN ["busybox", "sh", "-c", …], # shellcheck shell=busybox) are ash. Before, these were ignored or misread, and the script was explained as bash or sh.
  • A script is in POSIX mode only when its shebang really starts bash in it (#!/bin/bash --posix, or env -S with bash --posix). Before, --posix anywhere on the shebang line counted, also in #!/usr/bin/env bash --posix, where bash does not start.
  • With notes.pitfalls off, more ⚠ notes are hidden: those for distribution aliases, \b in [[ =~ ]], a Dockerfile RUN without pipefail, Python subprocess, GitHub Actions ${{ … }} injection and bats files. Before, these ignored the setting.
  • Quick fixes are offered in every language of the bashExplainer.languages setting, as the hover is. Before, they were offered in shellscript documents only.
  • On the web page's compare view, the Problems list shows the script's problems. Before, it stayed empty and the page raised an error.
  • Scripts that assign an element before a function (n[0]=5 f) are explained; before, explaining them failed. A long chain of functions that call each other in $( ) is explained at once; before, 40 of them froze the explanation for about a minute.

All changes