| 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 | "$@"
|