| 1 | #!/usr/bin/env bash
 | 
| 2 | #
 | 
| 3 | # Nice examples from blog post feedback.
 | 
| 4 | #
 | 
| 5 | # Usage:
 | 
| 6 | #   ./hard-coded-descriptors.sh <function name>
 | 
| 7 | 
 | 
| 8 | set -o nounset
 | 
| 9 | set -o pipefail
 | 
| 10 | set -o errexit
 | 
| 11 | 
 | 
| 12 | # https://lobste.rs/s/bqftd6/avoid_directly_manipulating_file
 | 
| 13 | #
 | 
| 14 | # - "One thing that I have found non-{0,1,2} FDs useful for however is when
 | 
| 15 | #   tools (e.g. gpg) take a --passphrase-fd argument, useful for when you are
 | 
| 16 | #   also redirecting some other thing into stdin."
 | 
| 17 | # - "Some protocols (like djb’s checkpassword or doit) rely on specific file
 | 
| 18 | #   descriptors"
 | 
| 19 | 
 | 
| 20 | # https://www.reddit.com/r/bash/comments/6td8j2/avoid_directly_manipulating_file_descriptors_in/
 | 
| 21 | 
 | 
| 22 | interactive-stdin() {
 | 
| 23 |   local path=$0  # This script
 | 
| 24 |   while read line <&"$fd" ; do
 | 
| 25 |     echo "[$line]"
 | 
| 26 |     read -p "Continue? " ans
 | 
| 27 |     [[ "$ans" != yes ]] && exit 1
 | 
| 28 |   done {fd}< $path
 | 
| 29 | }
 | 
| 30 | 
 | 
| 31 | # Here stdin conflicts
 | 
| 32 | interactive-stdin-bad() {
 | 
| 33 |   local path=$0  # This script
 | 
| 34 |   while read line;  do
 | 
| 35 |     echo "[$line]"
 | 
| 36 |     read -p "Continue? " ans
 | 
| 37 |     [[ "$ans" != yes ]] && exit 1
 | 
| 38 |   done < $path
 | 
| 39 | }
 | 
| 40 | 
 | 
| 41 | # Alternative way without explicit descriptors.
 | 
| 42 | interactive-stdin-alternative() {
 | 
| 43 |   local path=$0  # This script
 | 
| 44 |   local tty=$(tty)  # e.g. /dev/pts/23
 | 
| 45 |   while read line;  do
 | 
| 46 |     echo "[$line]"
 | 
| 47 |     read -p "Continue? " ans < $tty
 | 
| 48 |     [[ "$ans" != yes ]] && exit 1
 | 
| 49 |   done < $path
 | 
| 50 | }
 | 
| 51 | 
 | 
| 52 | # https://www.reddit.com/r/oilshell/comments/6tch5v/avoid_directly_manipulating_file_descriptors_in/
 | 
| 53 | 
 | 
| 54 | log-dates() {
 | 
| 55 |   exec {fd}> >(while IFS= read -r line; do printf "[%s] %s\n" "$(date)" "$line"; done)
 | 
| 56 |   echo "Hello" >&"$fd"
 | 
| 57 |   echo "Goodbye" >&"$fd"
 | 
| 58 | }
 | 
| 59 | 
 | 
| 60 | tee-like() {
 | 
| 61 |   local log=_tmp/tee-like.txt
 | 
| 62 | 
 | 
| 63 |   >$log  # truncate
 | 
| 64 | 
 | 
| 65 |   exec {fd}> >(while IFS= read -r line; do printf "%s\n" "$line"; printf "%s\n" "$line" >&3; done 3>>"$log")
 | 
| 66 |   echo "Hello" >&"$fd"
 | 
| 67 |   echo "Goodbye" >&"$fd"
 | 
| 68 | 
 | 
| 69 |   echo
 | 
| 70 |   echo "Contents of $log:"
 | 
| 71 |   cat $log
 | 
| 72 | }
 | 
| 73 | 
 | 
| 74 | pipe-stderr() {
 | 
| 75 |   local log=_tmp/pipe-stderr.log
 | 
| 76 |   set +o errexit
 | 
| 77 | 
 | 
| 78 |   ls -l /etc/passwd not_a_file 3>&1 1>&2 2>&3 \
 | 
| 79 |     | awk '{print "ERROR:" $0}' >$log
 | 
| 80 | 
 | 
| 81 |   # Another idiom, this seems wrong because of 3>&-
 | 
| 82 |   exec 3>&1
 | 
| 83 |   ls -l /etc/passwd 'not_a_file_2' 2>&1 >&3 3>&- \
 | 
| 84 |     | awk '{print "ERROR:" $0}' >>$log
 | 
| 85 |   exec 3>&-
 | 
| 86 | 
 | 
| 87 |   echo
 | 
| 88 |   echo "Contents of $log:"
 | 
| 89 |   cat $log
 | 
| 90 | }
 | 
| 91 | 
 | 
| 92 | _zip() {
 | 
| 93 |   ( # use a subshell to ensure the FD changes don't affect the caller
 | 
| 94 |   exec 4<"$1" 5<"$2"
 | 
| 95 |   while IFS="" read -u 4 a && read -u 5 b; do
 | 
| 96 |     printf "%s %s\n" "$a" "$b"
 | 
| 97 |   done
 | 
| 98 |   )
 | 
| 99 | }
 | 
| 100 | 
 | 
| 101 | zip-demo() {
 | 
| 102 |   seq 1 5 > _tmp/left.txt
 | 
| 103 |   seq 5 10 > _tmp/right.txt
 | 
| 104 |   _zip _tmp/{left,right}.txt 
 | 
| 105 | }
 | 
| 106 | 
 | 
| 107 | 
 | 
| 108 | _work() {
 | 
| 109 |   local id=$1
 | 
| 110 |   echo "Job $id"
 | 
| 111 |   for i in 1 2 3; do
 | 
| 112 |     echo "... $i ..."
 | 
| 113 |     sleep 0.2
 | 
| 114 |   done
 | 
| 115 | }
 | 
| 116 | 
 | 
| 117 | # man flock
 | 
| 118 | _flock() {
 | 
| 119 |   (
 | 
| 120 |     flock -n 9 || {
 | 
| 121 |       echo "Another job is already running; aborting"
 | 
| 122 |       exit 1
 | 
| 123 |     }
 | 
| 124 |     "$@"
 | 
| 125 | 
 | 
| 126 |   ) 9>_tmp/mylockfile
 | 
| 127 | }
 | 
| 128 | 
 | 
| 129 | flock-demo() {
 | 
| 130 |   _work A
 | 
| 131 |   _work B
 | 
| 132 | 
 | 
| 133 |   # Instead of running
 | 
| 134 |   $0 _flock _work C &
 | 
| 135 |   $0 _flock _work D &  # One is already running
 | 
| 136 |   wait
 | 
| 137 |   wait
 | 
| 138 | }
 | 
| 139 | 
 | 
| 140 | "$@"
 |