about summary refs log tree commit diff
diff options
context:
space:
mode:
authordana <dana@dana.is>2018-06-04 10:04:27 -0500
committerOliver Kiddle <okiddle@yahoo.co.uk>2018-06-07 18:19:55 +0200
commit64ef1eddfd4fa79a0720945e189cf7c3a44bde9c (patch)
tree9237ef4615c0643b783d5fac4f007f9cb4fc693d
parent594f2ff06e85bf27b154dd703ee3b2dd7f168bc0 (diff)
downloadzsh-64ef1eddfd4fa79a0720945e189cf7c3a44bde9c.tar.gz
zsh-64ef1eddfd4fa79a0720945e189cf7c3a44bde9c.tar.xz
zsh-64ef1eddfd4fa79a0720945e189cf7c3a44bde9c.zip
42931: completion for several utilities especially for checksums across a variety of systems
-rw-r--r--ChangeLog7
-rw-r--r--Completion/BSD/Command/_cksum67
-rw-r--r--Completion/Unix/Command/_base6459
-rw-r--r--Completion/Unix/Command/_chroot53
-rw-r--r--Completion/Unix/Command/_cksum112
-rw-r--r--Completion/Unix/Command/_lz4103
-rw-r--r--Completion/Unix/Command/_md5sum63
-rw-r--r--Completion/Unix/Command/_readlink48
-rw-r--r--Completion/Unix/Command/_shasum24
-rw-r--r--Completion/Unix/Command/_tee32
10 files changed, 486 insertions, 82 deletions
diff --git a/ChangeLog b/ChangeLog
index 9f958fb7f..81478847b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2018-06-07  Oliver Kiddle  <okiddle@yahoo.co.uk>
 
+	* 42931: dana: Completion/Unix/Command/_base64,
+	Completion/Unix/Command/_chroot, Completion/Unix/Command/_cksum,
+	Completion/Unix/Command/_lz4, Completion/Unix/Command/_md5sum,
+	Completion/Unix/Command/_tee, Completion/Unix/Command/_shasum,
+	Completion/Unix/Command/_readlink: completion for several
+	utilities especially for checksums across a variety of systems
+
 	* users/23434: Functions/Misc/zed: initialise UNDO_LIMIT_NO so
 	that an initial undo doesn't clear the whole edit buffer
 
diff --git a/Completion/BSD/Command/_cksum b/Completion/BSD/Command/_cksum
deleted file mode 100644
index 4f5b68d8a..000000000
--- a/Completion/BSD/Command/_cksum
+++ /dev/null
@@ -1,67 +0,0 @@
-#compdef cksum sum=cksum md2 md4 md5 rmd160 sha1 sha256 sha384 sha512 sha512t256
-
-if [[ $OSTYPE == (dragonfly|freebsd)* && service == cksum ]]; then
-  _arguments -s -S -A '-*' \
-    '-o[use specified historic algorithm]:historic algorithm:(1 2 3)' \
-    '*:_files'
-
-else
-  local -a args
-
-  case $OSTYPE in
-    dragonfly*|freebsd*|netbsd*)
-      args+=(
-        '-t[run built-in time trial]'
-      )
-      ;|
-    dragonfly*|freebsd*|openbsd*)
-      args+=(
-        '-r[reverse the output format]'
-      )
-      ;|
-    dragonfly*)
-      args+=(
-        '-b+[begin at specified offset]:begin offset: '
-        '-e+[end at specified offset]:end offset: '
-      )
-      ;;
-    freebsd*)
-      args+=(
-        '-c+[compare digest against specified string]:digest string: '
-      )
-      ;;
-    netbsd*)
-      args+=(
-        '(*)-c+[compare checksums against specifed sumfile]:sumfile:_files'
-        '-n[reverse the output format]'
-        '-w[warn on malformed checksum files]'
-      )
-      if [[ $service == cksum ]]; then
-        args=(
-          '(-o)-a+[use the specfied algorithm(s)]:algorithm:(crc md2 md4 md5 old1 old2 rmd160 sha1 sha256 sha384 sha512)'
-          '(-a)-o+[use the specfied historic algorithm]:historic algorithm:(1 2)'
-        )
-      fi
-      ;;
-    openbsd*)
-      args+=(
-        '-b[output in base 64]'
-        '-C+[compare checksums against specifed checklist]:checklist:_files'
-        '-c[file arguments are checklists]'
-        '-h+[output to specified hashfile]:hashfile:_files'
-        '*-t[run built-in time trial]'
-      )
-      if [[ $service == cksum ]]; then
-        args+=('*-a+[use the specfied algorithm(s)]:algorithm:_values -s , algotithm cksum md5 rmd160 sha1 sha224 sha256 sha384 sha512/256 sha512')
-      fi
-      ;;
-  esac
-
-  _arguments -s -S -A '-*' \
-    $args \
-    '-p[echo stdin to stdout]' \
-    '-q[quiet mode]' \
-    '-s+[checksum specified string]:string: ' \
-    '-x[run built-in test]' \
-    '*:_files'
-fi
diff --git a/Completion/Unix/Command/_base64 b/Completion/Unix/Command/_base64
new file mode 100644
index 000000000..47a650343
--- /dev/null
+++ b/Completion/Unix/Command/_base64
@@ -0,0 +1,59 @@
+#compdef base64 gbase64 base32 gbase32
+
+# Note: This does NOT cover `uuencode`/`uudecode`, which on some BSD systems
+# have the aliases `b64encode`/`b64decode` — those are sufficiently different
+# that they belong in another function. It DOES cover GNU's `base32`, since it
+# is essentially the same program as their `base64`.
+
+local variant type=base${service//[^2346]/}
+
+_pick_variant -r variant \
+  gnu='Free Soft' fourmilab=fourmi darwin=-D unix --version
+
+case $variant in
+  gnu)
+    _arguments -s -S : \
+      '(: -)--help[display help information]' \
+      '(: -)--version[display version information]' \
+      + dec \
+      '(enc -d --decode)'{-d,--decode}"[decode input from $type]" \
+      '(enc -i --ignore-garbage)'{-i,--ignore-garbage}'[ignore irrelevant characters when decoding]' \
+      + '(enc)' \
+      '(dec)'{-w+,--wrap=}'[wrap encoded lines at specified number of columns]:number of columns' \
+      + in \
+      '1:input file:_files'
+    return
+    ;;
+  darwin)
+    _arguments -s -S : \
+      '(: -)'{-h,--help}'[display help information]' \
+      + '(dec)' \
+      '(enc)'{-D,--decode}"[decode input from $type]" \
+      + '(enc)' \
+      '(dec)'{-b+,--break=}'[wrap encoded lines at specified number of columns]:number of columns' \
+      + '(out)' \
+      {-o+,--output=}'[specify output file]:output file:_files' \
+      + '(in)' \
+      {-i+,--input=}'[specify input file]:input file:_files' \
+      '1:input file:_files'
+    return
+    ;;
+  fourmilab)
+    _arguments -s -S : \
+      '(: -)--copyright[display copyright information]' \
+      '(: -)'{-u,--help}'[display help information]' \
+      '(: -)--version[display version information]' \
+      + dec \
+      '(enc -d --decode)'{-d,--decode}"[decode input from $type]" \
+      '(enc -n --noerrcheck)'{-n,--noerrcheck}'[ignore errors when decoding]' \
+      + '(enc)' \
+      '(dec)'{-e,--encode}"[encode input to $type]" \
+      + io \
+      '1:input file:_files' \
+      '2:output file:_files'
+    return
+    ;;
+esac
+
+# A few other implementations exist, though they are rarely encountered
+_default
diff --git a/Completion/Unix/Command/_chroot b/Completion/Unix/Command/_chroot
new file mode 100644
index 000000000..516992694
--- /dev/null
+++ b/Completion/Unix/Command/_chroot
@@ -0,0 +1,53 @@
+#compdef chroot gchroot
+
+local variant ret=1
+local -a context line state state_descr args
+local -A opt_args
+
+_pick_variant -r variant gnu='Free Soft' unix --version
+
+variant+=-$OSTYPE
+
+case $variant in
+  gnu-*)
+    args=(
+      '(: -)--help[display help information]'
+      '(: -)--version[display version information]'
+      '--groups=[specify supplemental group memberships]: :_sequence -s , _groups'
+      '--userspec=[specify user and group to run process as]: :->userspecs'
+      '--skip-chdir[do not change working directory to /]'
+    )
+    ;;
+  *-openbsd*)
+    args=(
+      '-u+[specify user to run process as]: :_users'
+      '-g+[specify group to run process as, and supplemental group memberships]: :_sequence -s , _groups'
+    )
+    ;;
+  *-(darwin|dragonfly|freebsd|netbsd)*)
+    args=(
+      '-u+[specify user to run process as]: :_users'
+      '-g+[specify group to run process as]: :_groups'
+      '-G+[specify supplemental group memberships]: :_sequence -s , _groups'
+    )
+    ;;
+esac
+
+args+=( '1:new root directory:_directories' '*:::command:_normal' )
+
+_arguments -s -S : $args && ret=0
+
+# @todo user:group specs are probably used often enough to justify making a type
+# function for this (see also `chown`, `cpio`, `rsync`, ...)
+[[ $state == userspecs ]] &&
+if compset -P '*:*:'; then
+  ret=1
+elif compset -P '*:'; then
+  _groups && ret=0
+elif compset -S ':*'; then
+  _users && ret=0
+else
+  _users -qS : && ret=0
+fi
+
+return ret
diff --git a/Completion/Unix/Command/_cksum b/Completion/Unix/Command/_cksum
new file mode 100644
index 000000000..bb2f60ac7
--- /dev/null
+++ b/Completion/Unix/Command/_cksum
@@ -0,0 +1,112 @@
+#compdef cksum gcksum sum gsum md2 md4 md5 rmd160 sha1 sha256 sha384 sha512 sha512t256 skein256 skein512 skein1024
+
+# This function covers mostly the BSD `cksum`, `sum`, and digest utilities. It
+# also covers the GNU Coreutils `cksum` and `sum`. For the GNU digest utilities,
+# see `_md5sum`. For the `shasum` Perl script, see `_shasum`.
+#
+# Notes:
+# - We really don't do a very good job of handling option exclusivity here. In
+#   particular, OpenBSD treats -t and -x as exclusive of each other and most
+#   other options — but there are issues with other variants too (@todo)
+# - Although only OpenBSD's documentation mentions it, -t can be supplied
+#   multiple times with all variants to run additional rounds of testing
+# - All digest variants on a given platform share code, but not all variants are
+#   found on all platforms (e.g., Darwin only has `md5`). Some BSDs are actively
+#   purging legacy/insecure digest tools
+# - The documentation for Dragonfly/FreeBSD `sum` says that it's 'identical to'
+#   `cksum`, but, as the synopsis confirms, it doesn't take any options
+# - FreeBSD's -c is never useful, and Dragonfly's -b/-e are *almost* never
+#   useful, with multiple input files
+# - NetBSD's -n isn't useful with `sum` and `cksum` unless a digest is specified
+#   with -a. Similarly, OpenBSD's -b isn't useful with `cksum` without -a
+# - OpenBSD's -a option allows you to add a b/x suffix to each algorithm name;
+#   we don't handle that. Also, only one -a option can be used in conjunction
+#   with -c; we don't handle that either
+
+local -a args
+
+_pick_variant gnu='Free Soft' unix --version && {
+  args=(
+    '*: :_files'
+    '(: -)--help[display help information]'
+    '(: -)--version[display version information]'
+  )
+  [[ $service == *cksum* ]] || args+=(
+    '(-s --sysv)-r[use BSD algorithm (1 KiB blocks)]'
+    '(-r -s --sysv)'{-s,--sysv}'[use System V algorithm (512 B blocks)]'
+  )
+  _arguments -s -S : $args
+  return
+}
+
+[[ $OSTYPE == (darwin|dragonfly|freebsd|openbsd)* && $service == sum ]] && {
+  _default
+  return
+}
+
+[[ $OSTYPE == (darwin|dragonfly|freebsd)* && $service == cksum ]] && {
+  _arguments -s -S -A '-*' \
+    '-o[use specified historic algorithm]:historic algorithm:(1 2 3)' \
+    '*: :_files'
+  return
+}
+
+case $OSTYPE in
+  darwin*|dragonfly*|freebsd*|netbsd*)
+    args+=(
+      '(-n -p -r)-q[output checksums only]'
+    )
+    ;| # MATCH AGAIN
+  darwin*|dragonfly*|freebsd*|openbsd*)
+    args+=(
+      '(-p -q)-r[reverse output format]'
+    )
+    ;| # MATCH AGAIN
+  netbsd*|openbsd*)
+    args+=(
+      '-c[verify checksums from input files]'
+    )
+    ;| # MATCH AGAIN
+  dragonfly*)
+    args+=(
+      '-b+[begin processing files at specified offset]:begin offset (bytes)'
+      '-e+[end processing files at specified offset]:end offset (bytes)'
+    )
+    ;;
+  freebsd*)
+    args+=(
+      '-c+[verify input against specified digest string]:digest string'
+    )
+    ;;
+  netbsd*)
+    args+=(
+      '(-p -q)-n[reverse output format]'
+      '(-p)-w[warn on malformed checksum files]'
+    )
+    [[ $service == (|ck)sum ]] && args+=(
+      '(-o)-a+[use specified algorithm]:algorithm:(crc md2 md4 md5 old1 old2 rmd160 sha1 sha256 sha384 sha512)'
+      '(-a)-o+[use specified historic algorithm]:historic algorithm:(1 2)'
+    )
+    ;;
+  openbsd*)
+    args+=(
+      '-b[output in base64]'
+      '(-h)-C+[verify input files against checksums in specifed file]:checksum file:_files'
+      '(-C)-h+[output checksums to specified file]:checksum file:_files'
+      '(-n -r)-q[output checksum only, or suppress check success messages]'
+    )
+    [[ $service == cksum ]] && args+=(
+      '*-a+[use specified algorithm(s)]:algorithm:_values -s , algorithm cksum md5 rmd160 sha1 sha224 sha256 sha384 sha512/256 sha512'
+    )
+    ;;
+esac
+
+args+=(
+  '-p[output stdin along with checksum]'
+  '*-s+[checksum specified string]:string'
+  '*-t[run built-in time trial(s)]'
+  '-x[run built-in tests]'
+  '*: :_files'
+)
+
+_arguments -s -S -A '-*' : $args
diff --git a/Completion/Unix/Command/_lz4 b/Completion/Unix/Command/_lz4
new file mode 100644
index 000000000..d69091d00
--- /dev/null
+++ b/Completion/Unix/Command/_lz4
@@ -0,0 +1,103 @@
+#compdef lz4 lz4c lz4c32 lz4cat unlz4
+
+# Notes:
+# - All lz4 CLI tools take the same options — you can do `unlz4 --compress` if
+#   you want — and we complete accordingly. One can make a reasonable argument
+#   that we shouldn't, but...?
+# - The only exceptions to the above are the legacy compression options (-c0,
+#   -hc, and so on) — only lz4c accepts these. Each of these options is
+#   interpreted separately otherwise (e.g., -c0 becomes equivalent to -c -0)
+# - All these tools use a non-standard option-handling method that we don't
+#   fully support. For example, the tool will let you do things like `-b1e3i3`
+#   instead of `-b1 -e3 -i3` — we won't
+
+local ret=1
+local -a context line state state_descr expl args levels=( -{1..16} )
+local -A opt_args val_args
+
+args=(
+  + excl # Fully exclusive options
+  '(: -)'{-h,--help}'[display help information]'
+  '(: -)-H[display long help information]'
+  '(: -)'{-V,--version}'[display version information]'
+  + misc # Misc. arguments
+  '(-q -v --quiet --verbose)*'{-q,--quiet}'[reduce output verbosity]'
+  '(-q -v --quiet --verbose)*'{-v,--verbose}'[increase output verbosity]'
+  '*::: :->files'
+  + B # Benchmark/compress-mode options (not allowed with legacy format)
+  '(d t -l)*-B-[specify block property]: :->block-props'
+  '(d t -l --no-content-size)--content-size[record original uncompressed size]'
+  '(d t -l --no-frame-crc)--frame-crc[enable content checksum]'
+  '(d t -l --content-size)--no-content-size[do not record original uncompressed size]'
+  '(d t -l --frame-crc)--no-frame-crc[disable content checksum]'
+  '(d t -l --sparse)--no-sparse[disable sparse-file support]'
+  '(d t -l --no-sparse)--sparse[enable sparse-file support]'
+  + C # Compress/decompress-mode options
+  '(b t -c --stdout --to-stdout)'{-c,--stdout}'[write on standard output]'
+  '(b t -y)'{-f,--force}'[overwrite target without prompting, or cat on standard output]'
+  '(b t -k --keep --rm)'{-k,--keep}'[keep source file]'
+  '(b t -m -r --multiple)'{-m,--multiple}'[take multiple input files]'
+  '!(b -t -f -y --force)--no-force'
+  '(b t -m --multiple)-r[operate recursively on directories]'
+  '(b t -k --keep)--rm[remove source file]'
+  '!(b t -c --stdout)--to-stdout'
+  + b # Benchmark-mode options
+  "(C c d t)-b-[benchmark file using specified compression level]::compression level:(${(j< >)levels//-/})"
+  "(C c d t)-e-[specify upper compression level limit (with -b)]:compression level:(${(j< >)levels//-/})"
+  '(C c d t)-i-[specifiy minimum evaluation time (with -b)]:evaluation time (seconds)'
+  + c # Compress-mode options
+  "(b d t ${(j< >)levels} -c0 -c1 -c2 -hc)"${^levels}
+  '(B b d t -m -r --multiple)-l[compress using legacy (Linux kernel) format]'
+  '(b d t -z --compress)'{-z,--compress}'[compress file]'
+  + d # Decompress-mode options
+  '(B b c d t)'{-d,--decompress}'[decompress file]'
+  '!(B b c d t)--uncompress'
+  + t # Test-mode options
+  '(B C b c d t)'{-t,--test}'[test integrity of compressed file]'
+)
+[[ $service == lz4c ]] && args+=(
+  + l # Legacy compress-mode options (not to be confused with the legacy format)
+  "(b d t ${(j< >)levels} -c1 -c2 -hc)-c0[use fast compression (like -0)]"
+  "(b d t ${(j< >)levels} -c0 -c2 -hc)-c1[use high compression (like -9)]"
+  "(b d t ${(j< >)levels} -c0 -c1 -c2 -hc)"{-c2,-hc}'[use very high compression (like -12)]'
+  '(b t -f --force)-y[overwrite target without prompting]'
+)
+
+_arguments -s -S : $args && ret=0
+
+case $state in
+  block-props)
+    # The usage help indicates that the use of an explicit byte value (-B32 or
+    # greater) is only for benchmarking, and indeed when such a value is given
+    # the tool prints a message prefixed with 'bench:'... but there is nothing
+    # that actually restricts this to the benchmark mode, so...?
+    _values 'predefined block property or block size in bytes (32+)' \
+      '4[set block size to 64 KiB]' \
+      '5[set block size to 256 KiB]' \
+      '6[set block size to 1 MiB]' \
+      '7[set block size to 4 MiB]' \
+      'D[enable block dependency]' \
+      'X[enable block checksum]' \
+    && ret=0
+    ;;
+  files)
+    if
+      (( CURRENT == 1 )) ||
+      [[ -n ${opt_args[(i)*-(-b|-m|-r|--multiple)]} ]]
+    then
+      if [[ -n ${opt_args[(i)*--r]} ]]; then
+        _description files expl 'input file or directory'
+      else
+        _description files expl 'input file'
+      fi
+      _files "${(@)expl}" && ret=0
+    elif (( CURRENT == 2 )); then
+      _description files expl 'output file'
+      _files "${(@)expl}" && ret=0
+    else
+      _message 'no more arguments' && ret=0
+    fi
+    ;;
+esac
+
+return ret
diff --git a/Completion/Unix/Command/_md5sum b/Completion/Unix/Command/_md5sum
index 073e6beba..8e93fbbac 100644
--- a/Completion/Unix/Command/_md5sum
+++ b/Completion/Unix/Command/_md5sum
@@ -1,15 +1,48 @@
-#compdef md5sum gmd5sum
-
-_arguments -S \
-  '(-b --binary)'{-b,--binary}'[read in binary mode]' \
-  '(-c --check)'{-c,--check}'[read MD5 sums from the FILEs and check them]' \
-  '--tag[create a BSD-style checksum]' \
-  '(-t --text)'{-t,--text}'[read in text mode]' \
-  "--ignore-missing[don't fail or report status for missing files]" \
-  '(-q --quiet)'{-q,--quiet}"[don't print OK for each successfully verified file]" \
-  '--status[no output, status code shows success]' \
-  '--strict[exit non-zero for improperly formatted checksum lines]' \
-  '(-w --warn)'{-w,--warn}'[warn about improperly formatted checksum lines]' \
-  '(-)--help[display help and exit]' \
-  '(-)--version[output version information and exit]' \
-  '*:files:_files'
+#compdef md5sum gmd5sum b2sum gb2sum sha1sum gsha1sum sha224sum gsha224sum sha256sum gsha256sum sha384sum gsha384sum sha512sum gsha512sum
+
+# This function covers the various digest utilities (which are all essentially
+# the same program) from GNU Coreutils. It does NOT cover GNU `cksum` and `sum`,
+# nor the various BSD digest utilities like `md5` and `sha1` — see `_cksum` for
+# all of those. See `_shasum` for the `shasum` Perl script.
+#
+# @todo Support BusyBox?
+
+local type
+local -a args
+
+case $service in
+  *md5*) type=MD5 ;;
+  *b2*)  type=BLAKE2 ;;
+  *sha*) type=SHA${service//[^0-9]/} ;;
+esac
+
+# General options
+args+=(
+  '(: -)--help[display help information]'
+  '(: -)--version[display version information]'
+)
+# Summing options
+args+=(
+  + sum
+  '(chk)--tag[create BSD-style checksums]'
+  '(chk -b -t --binary --text)'{-b,--binary}'[read in binary mode]'
+  '(chk -b -t --binary --text)'{-t,--text}'[read in text mode]'
+)
+# This is the only option that differs amongst all of these tools
+[[ $service == *b2* ]] && args+=(
+  '(chk -l --length)'{-l+,--length=}'[specify digest length]:digest length (bits, multiples of 8)'
+)
+# Verification options
+args+=(
+  + chk
+  '(sum -c --check)'{-c,--check}"[verify $type checksums from input files]"
+  "(sum)--ignore-missing[don't fail or report status for missing files]"
+  "(sum)--quiet[don't print OK for each verified file]"
+  '(sum -w --warn)--status[suppress all output]'
+  '(sum)--strict[exit non-zero for improperly formatted checksum lines]'
+  '(sum -w --status --warn)'{-w,--warn}'[warn about each improperly formatted checksum line]'
+)
+# Operands
+args+=( '*: :_files' )
+
+_arguments -s -S : $args
diff --git a/Completion/Unix/Command/_readlink b/Completion/Unix/Command/_readlink
new file mode 100644
index 000000000..36bd43752
--- /dev/null
+++ b/Completion/Unix/Command/_readlink
@@ -0,0 +1,48 @@
+#compdef readlink greadlink
+
+local variant ret=1
+local -a context line state state_descr args copts aopts=( -A '-*' )
+local -A opt_args
+
+# We can't use groups here because it would complicate the option filtering
+copts=( -e -f -m --canonicalize --canonicalize-existing --canonicalize-missing )
+
+args=(
+  '(: -)--help[display help information]'
+  '(: -)--version[display version information]'
+  # Delimiter options
+  # (Note: GNU `readlink` won't let you use -n with multiple files)
+  '(-n -z --no-newline --zero)'{-n,--no-newline}'[suppress trailing newline]'
+  '(-n -z --no-newline --zero)'{-z,--zero}'[use NUL as output delimiter]'
+  # Verbosity options
+  '(-q -s -v --quiet --silent --verbose)'{-q,-s,--quiet,--silent}'[suppress most error messages]'
+  '(-q -s -v --quiet --silent --verbose)'{-v,--verbose}'[show error messages]'
+  # Canonicalisation options
+  "(${(j< >)copts})"{-e,--canonicalize-existing}'[canonicalize paths (all components must exist)]'
+  "(${(j< >)copts})"{-f,--canonicalize}'[canonicalize paths]'
+  "(${(j< >)copts})"{-m,--canonicalize-missing}'[canonicalize paths (components may be missing)]'
+)
+
+# Filter out non-GNU options if applicable
+if _pick_variant gnu='Free Soft' unix --version; then
+  aopts=( )
+else
+  case $OSTYPE in
+    darwin*)          args=( ${(@M)args:#(|*\))-[n]\[*} ) ;;
+    netbsd*)          args=( ${(@M)args:#(|*\))-[fnqsv]\[*} ) ;;
+    dragonfly*|*bsd*) args=( ${(@M)args:#(|*\))-[fn]\[*} ) ;;
+    *) args=( ) ;;
+  esac
+fi
+
+_arguments -s -S $aopts : $args '*: :->files' && ret=0
+
+# File arguments must be symlinks unless a canonicalisation option is given
+[[ $state == files ]] &&
+if [[ ${opt_args[(i)(${~${(j<|>)copts}})]} ]]; then
+  _files && ret=0
+else
+  _files -g '*(@)' && ret=0
+fi
+
+return ret
diff --git a/Completion/Unix/Command/_shasum b/Completion/Unix/Command/_shasum
new file mode 100644
index 000000000..950a47763
--- /dev/null
+++ b/Completion/Unix/Command/_shasum
@@ -0,0 +1,24 @@
+#compdef shasum -P shasum(|5).*
+
+# This function covers the `shasum` script included with Perl (and notably the
+# primary SHA digest tool on macOS). See `_md5sum` for the GNU digest utilities
+# and `_cksum` for the BSD ones.
+#
+# @todo It's possible that someone could symlink this script to `sha1sum` or
+# whatever; we might want to detect that some day.
+
+_arguments -s -S : \
+  '(: -)'{-h,--help}'[display help information]' \
+  '(: -)'{-v,--version}'[display version information]' \
+  + '(sum)' \
+  '(chk)'{-0,--01}'[read in BITS mode]' \
+  '(chk)'{-b,--binary}'[read in binary mode]' \
+  '(chk)'{-p,--portable}'[read in portable mode]' \
+  '(chk)'{-t,--text}'[read in text mode]' \
+  + 'chk' \
+  '(sum)'{-c,--check}'[verify checksums from input files]' \
+  '(sum -s -w --status --warn)'{-s,--status}'[suppress all output]' \
+  '(sum -s -w --status --warn)'{-w,--warn}'[warn about each improperly formatted checksum line]' \
+  + misc \
+  '(-a --algorithm)'{-a+,--algorithm=}'[specify algorithm]:algorithm:(1 224 256 384 512 512224 512256)' \
+  '*: :_files'
diff --git a/Completion/Unix/Command/_tee b/Completion/Unix/Command/_tee
new file mode 100644
index 000000000..66ef66e19
--- /dev/null
+++ b/Completion/Unix/Command/_tee
@@ -0,0 +1,32 @@
+#compdef tee gtee
+
+local ret=1
+local -a context line state state_descr args
+local -A opt_args
+
+args=(
+  '(: -)--help[display help information]'
+  '(: -)--version[display version information]'
+  '(-a --append)'{-a,--append}'[append to files instead of overwriting]'
+  '(-i --ignore-interrupts)'{-i,--ignore-interrupts}'[ignore interrupt signals]'
+  '(--output-error)-p[warn on errors writing to non-pipes]'
+  '(-p)--output-error=[specify write-error behavior]: :->errmodes'
+)
+
+# Filter out non-GNU options if applicable
+_pick_variant gnu='Free Soft' unix --version ||
+args=( ${(@M)args:#(|*\))-[ai]\[*} )
+
+_arguments -s -S : $args '*: :_files' && ret=0
+
+[[ $state == errmodes ]] && {
+  args=(
+    'exit[exit on errors writing to any output]'
+    'exit-nopipe[exit on errors writing to non-pipes]'
+    'warn[warn on errors writing to any output]'
+    'warn-nopipe[warn on errors writing to non-pipes]'
+  )
+  _values 'error mode' $args && ret=0
+}
+
+return ret