Skip to content

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 09:47
· 1243 commits to main since this release
  • The web page's shell sample is a tour of every feature, in sections: basics, commands with worked examples, the pitfalls pointed out, and tricky syntax; it was a short script of 15 lines.
  • -a and -o in [ ] are grouped as bash does: [ "$a" -eq 1 -a "$b" -eq 2 ] is two comparisons joined by AND, [ ! a = b -a -f c ] is !(a = b) AND -f c, and [ -a file ] and [ -o noclobber ] are the unary tests; they were mis-grouped (("$a" -eq 1 -a "$b") -eq 2, -f (a -a (-f b)), !(a = b -a -f c)), and -a file was an "arithmetic unary operator". -g, -u, -k, -O, -G, -N and -R are explained as tests too.
  • ${a[@]:1:2} and ${@:2} are explained as taking elements (of the array, or the arguments from $2), with examples checked in bash; they were explained, with a wrong example, as taking characters.
  • An assignment with only a redirection (FOO=1 2>/dev/null) is explained as an assignment in the shell; it was "the external command ``".
  • perl one-liners that leave the -n/-p loop with last (perl -ne 'last if /^END/; print') get worked examples, as perl runs them: the loop stops, END still runs.
  • jq filters with .a.[0] (jq 1.7) or a def after a comma (1, def f: 2; f) are explained; before, the whole filter was "could not parse".
  • git log --date=relative examples over a year old read like git's: "1 year, 1 month ago" up to five years, instead of "1 year ago".
  • perl one-liners with a BEGIN { … } or END { … } block followed by more code without ; (perl -lne 'END { print $n } $n++') are explained statement by statement; the block and the code after it were taken for one assignment.
  • exec 3>&- and exec 3<&- are explained again as closing the descriptor; since the parser update in 0.3.0 they were only "redirection >&-".
  • A Jenkinsfile sh step starting with a shebang (#!/bin/bash, #!/usr/bin/env zsh) is read as that shell; the shebang was misread before.
  • ** in file-name globs is explained as any depth of directories, as it is meant, with examples such as src/lib/error.ts. When no shopt -s globstar comes before it, a pitfall says that bash then treats ** as * (one level only), unless a file read with source turns it on; after shopt -u globstar, and in shells without globstar (sh, dash…), it is explained as *. zsh always has it. Examples no longer show names like appapp/app.ts. The options of shopt (globstar, nullglob, dotglob, extglob…) are explained.
  • Glob examples follow shopt -s dotglob (names starting with . match too), nocaseglob (file names ignore case) and nocasematch (case and [[ == ]] patterns ignore case), set earlier in the script; the explanation says which is on.
  • Extended globs (!(*.txt), *.@(jpg|png), +([0-9])…) in arguments are read as one word; before, the parser took !(…) for a subshell and explained *.txt as a command. They are explained part by part, with matching and non-matching names. A pitfall points out a missing shopt -s extglob (bash's syntax error), or one inside the same function or block as the glob, which comes too late because bash reads the whole block first. In zsh, *(…) is noted as zsh-only syntax (glob qualifiers), as before.
  • A pitfall for for f in *.log (and "$dir"/*): when nothing matches, the loop runs once with the pattern itself. It suggests [ -e "$f" ] || continue or shopt -s nullglob, and stays quiet when the body already checks, or with nullglob or failglob on.
  • A pitfall for variables set on the right of a pipe and read after it (cat f | while read -r l; do n=…; done; echo "$n"): the pipeline ran in a subshell. It suggests done < file, < <(command) or shopt -s lastpipe, and stays quiet in zsh and ksh, where the last part runs in the shell itself.
  • Aliases defined in the script (alias ll='ls -l') are explained where they are used, with a pitfall that bash does not expand them in scripts without shopt -s expand_aliases.
  • Variables of CI systems are explained: about 100 environment variables that GitHub Actions, GitLab CI/CD, CircleCI, Jenkins, Bitbucket Pipelines and Azure Pipelines set ($GITHUB_WORKSPACE, $CI_COMMIT_SHA, $BUILD_NUMBER…), unless the script sets them itself; the contexts of GitHub Actions expressions (${{ github.head_ref }}, secrets.*, matrix.*, steps.<id>.outputs.*…); Azure Pipelines macros ($(Build.SourcesDirectory)); and Jenkins' ${env.BRANCH_NAME} and ${params.X}.
  • Fixes found by new tests: $"…" (a translated string) is explained as such instead of as a plain string without its $; unset explains its options and names; a Python shell=True script with \x41 or \u0041 escapes is decoded as Python does; perl one-liners with elsif get worked examples.
  • The pitfall for rm -rf "$DIR"/… no longer fires on special parameters such as "$$/x", where it suggested ${VAR:?}.
  • git log examples with %<(1,trunc) show .., as git does, instead of the first character; a width of 0, which git does not take, gets no example.
  • perl one-liners: s{a{1}}{b} (nested brackets) and s{a} {b} are split into pattern and replacement as perl does, and s/a// reads "delete" instead of "replace with ``".
  • awk: the pattern 0 is explained as always false; it was "always true (non-zero)".
  • Regex examples: a{,2} in grep -E/sed -E (and a\{,2\} in basic regexes) is read as "at most 2", as GNU tools do, instead of as the text {,2}; in grep -P, []a] is a class of ] and a instead of a class that matches nothing.
  • printf '%c' "" in examples prints a NUL byte, as bash does.
  • Extended globs such as !(app)*, where !(…) is followed by a part of varying length, get no examples: bash may end !(…) at any point, so names like appapp match, which the examples said did not.
  • mikefarah/yq v3 paths: (.==x) is explained as the elements equal to x, instead of the elements whose . matches x.
  • sed examples follow GNU sed in more cases: \L/\U cancel a pending \u/\l (s/.*/\u\L&/ is all lower case); an empty regex (//) is the last one used when the line runs, not the one written before it; and scripts GNU sed rejects (\9 for a group the pattern lacks, // with no regex used yet) get no example instead of a made-up output.
  • Shell dialects: select loops and read -n1 (a value attached to -n, -t…) are noted in scripts for shells that lack them; the parser reads select as a for loop, and only separate values were seen.
  • Examples for ${v#[[:digit:]]} and other patterns with POSIX classes ([[:alpha:]], [[:space:]]…) follow bash; the class was cut at its first ].
  • coreutils examples: sort -h puts 0K with the zeros, as GNU sort does, and tr a '' gets no example (GNU tr rejects an empty second set).
  • Pipeline and awk examples: grep -o -v prints nothing, as GNU grep does, instead of the non-matching lines; awk -Ft splits on the letter t, as gawk and mawk do, not on tabs (-F'\t' is a tab).
  • Host files: a GitHub Actions step with shell: pwsh (or any shell: of its own) is no longer read with the shell: of the step before it; a Dockerfile exec-form string with \n no longer shifts the lines after it; a Python call with comments between its arguments (run( # why) is found.
  • awk examples follow gawk and mawk in more cases: substr() truncates its numbers and keeps the length when the start is before 1 (substr("hello", 0, 2) is he); \\ in a sub()/gsub() replacement stays two backslashes unless & follows; programs gawk rejects (an array used as a value, (a, b) outside in, a newline right after (, a built-in without its argument) get no example.
  • perl examples follow perl in more cases: ! and not gave the opposite truth value; reverse 1..3 reverses the list; split " " and split alone split on blanks (the latter crashed); a match in list context gives its groups (my @m = /(\w+) (\d+)/); tr/a/b/s squeezes only translated characters; next inside a for loop goes on with the next item; with -F, fields are split with the newline, as perl does; "\Lab\uc" is abc. Code perl rejects or that depends on unknown values (sort { … } with nothing to sort, "@a[0]", "a@b.c") gets no example.
  • jq examples follow jq in more cases: round takes halves away from zero (-2.5 → -3); try keeps the values output before an error; an error on the left of // is reported, not replaced by the default; @base64d fails on text it cannot decode; assigning to a negative index out of range (.[-5] |= 1) is an error; $__loc__ names <top-level>. Filters jq rejects or that differ between jq releases ({a: .b | f}, {(…)}, ascii, |= empty) get no example.
  • time is explained as bash's keyword that times the command after it (-p for the POSIX format), and that command is explained as usual; it was an unknown external command.
  • About 70 more commands that distributions install by default: flock, journalctl, ping, dig, lsof, free, top, crontab, setfacl, unshare, nsenter, mkfs.ext4, ssh-add, shuf, vim, … (with their frequent options).
  • [[ -f a && $x =~ re ]], [[ -f a && $x = y ]] and [[ $x = *.txt || $x = *.sh ]] (= and =~ next to && and ||, = with a glob on the right), [[ $x = *"$y"*z ]] and [[ $x = ~/a* ]] (a pattern of several parts, or starting with ~, after =, == and !=), [[ ! $x =~ re ]], [[ ! $x = y ]], [[ ! $a -eq 1 ]], [[ ! $a < b ]] and [ ! a = b ] (! negates the whole test), and ${s:$i}, ${s:$i+1} and ${s:$i-1:1} are read as bash does; the parser's grammar is patched so that = and =~ in [[ ]] bind tighter than && and ||, = takes a pattern like == does (with any number of quoted and expanded parts in it), ! binds looser than the comparison after it, and a variable can be the offset or length of a substring, with or without arithmetic after it. Before, [[ -f a && $x =~ re ]] was read as (-f a && $x) =~ re, [[ $x = *.txt || $x = *.sh ]] as a syntax error, [[ ! $x =~ re ]] as (! $x) =~ re, and ${s:$i} was a syntax error.
  • Aliases and functions that distributions define for interactive shells are explained, with what they stand for on each system, as checked in the files the distributions ship: Ubuntu's ll, la, l and alert; Fedora / RHEL's ll and l.; openSUSE's ll, la, l, ls-l, o, .., ..., cd.., +, -, md, rd, rehash, beep, unmount, remount and xd; Git for Windows' ll. A note says they depend on the system and are missing in scripts, and what to write instead; in Markdown code blocks, typed into a terminal, the note is milder.