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