You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.