variable scope bug and condition on new file
[tee-unique] / appendu
1 #!/bin/bash
2 # Copyright (C) 2014 Ian Kelling
3 # This program is under GPL v. 3 or later, see <http://www.gnu.org/licenses/>
4
5 appendu() {
6 local help="Usage: appendu [OPTION]... FILE [LINE_SET]...
7
8 Append unique.
9
10 A LINE_SET is one or more lines. Append LINE_SET to FILE if it does not exist in
11 FILE. If no LINE_SET argument is given, read lines from stdin, and treat each
12 as a single LINE_SET. Appended text is output to the terminal. Duplicate
13 LINE_SETs are treated the same.
14
15 -- stop processing arguments
16 [-h|--help] display this message"
17
18
19 while true; do
20 if [[ $1 == --help || $1 == -h ]]; then
21 echo "$help"
22 return
23 elif [[ $1 == -- ]]; then
24 shift
25 break
26 else
27 break
28 fi
29 done
30
31 if [[ ${#@} == 0 ]]; then
32 echo "error: need 1 or more arguments"
33 echo "$help"
34 return 1
35 fi
36
37 local file="$1"
38 shift
39
40 local new_file=true
41 if [[ -e $file ]]; then
42 new_file=false
43 else
44 local dir="$(dirname "$file")"
45 if [[ ! -d $dir ]]; then
46 echo "appendu error: $dir does not exist"
47 return 1
48 fi
49 fi
50 if ! $new_file; then
51 if [[ ! -r $file ]]; then
52 echo "appendu error: cannot read or write $file"
53 return 1
54 fi
55 if [[ ! -w $file ]]; then
56 echo "appendu error: cannot read or write $file"
57 return 1
58 fi
59 fi
60
61 local strings
62 if (( $# == 0 )); then
63 unset IFS
64 while read -r x; do
65 strings+=( "$x" )
66 done
67 else
68 strings=( "$@" )
69 fi
70
71 if $new_file; then
72 # fix files with no newline at the end.
73 # the following command won't work right on them otherwise.
74 # e = run script, $a\ means append following text, but there is none,
75 # so sed only does what it always does when it was supposed to modify a file,
76 # which is append a newline if there was none.
77 sed -ie '$a\' "$file"
78 # command substitution removes any trailing newlines, so we have to add
79 # a non-newline ending, we randomly chose "b", then remove it.
80 local content=$(cat "$file"; echo b) content=${content%b}
81 fi
82
83 # we aren't using regex because we want to match strings,
84 # but we also want our match to start at the beginning of a line,
85 # or the beginning of the file, and to end at a line ending.
86 # So we do some slick bash to match this.
87 local start="?(*
88 )"
89 local end="
90 *"
91 local return_code string return_code
92 for string in "${strings[@]}"; do
93 if $new_file || [[ $content != $start"$string"$end ]]; then
94 if ! tee -a "$file"<<<"$string"; then
95 return_code=$?
96 echo "appendu error: error writing to $file"
97 return $return_code
98 fi
99 fi
100 done
101 return 0
102 }
103 appendu "$@"