use strings, not regex, and allow multi-line args
[tee-unique] / appendu-function
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.
13
14 -s don't try to use sudo when it would help us read or write the file
15 -- stop processing arguments
16 --help display this message"
17
18 local readsudo writesudo x strings string
19 local dosudo=true
20
21 while true; do
22 if [[ $1 == --help ]]; then
23 echo "$help"
24 return
25 elif [[ $1 == -s ]]; then
26 dosudo=false
27 shift
28 elif [[ $1 == -- ]]; then
29 shift
30 break
31 else
32 break
33 fi
34 done
35
36 if [[ ${#@} == 0 ]]; then
37 echo "error: need 1 or more arguments"
38 echo "$help"
39 return 1
40 fi
41
42 local file="$1"
43 shift
44
45 local readsudo=false
46 local file_exists=false
47 if [[ -e $file ]]; then
48 file_exists=true
49 [[ -r $file ]] || readsudo=true
50 [[ -w $file ]] || writesudo=sudo
51 else
52 local dir="$(dirname "$file")"
53 if [[ -d $dir ]]; then
54 [[ ! -w $dir ]] && writesudo=sudo
55 else
56 echo "appendu error: $dir does not exist"
57 return 1
58 fi
59 fi
60 if ! $dosudo; then
61 readsudo=
62 writesudo=
63 fi
64
65 if (( $# == 0 )); then
66 unset IFS
67 while read -r x; do
68 strings+=( "$x" )
69 done
70 else
71 strings=( "$@" )
72 fi
73
74 if $file_exists; then
75 # fix files with no newline at the end.
76 # the following command won't work right on them.
77 # e = run script, $a\ means append following text, but there is none,
78 # so sed only does what it always does when it was supposed to modify a file,
79 # which is append a newline if there was none.
80 sed -ie '$a\' "$file"
81 # this removes any trailing newline in the var, so we add it back on,
82 # because we want a consistent ending to match
83 local file_content
84 if $readsudo; then
85 file_content="$(sudo cat "$file")
86 "
87 else
88 file_content="$(<"$file")
89 "
90 fi
91 # we aren't using regex because we want to match strings,
92 # but we also want our match to start at the beginning of a line,
93 # or the beginning of the file, and to end at a line ending.
94 # So we do some slick bash to match this.
95 local start="?(*
96 )"
97 local end="
98 *"
99 for string in "${strings[@]}"; do
100 [[ $file_content != $start"$string"$end ]] && $writesudo tee -a "$file"<<<"$string"
101 done
102 else
103 for string in "${strings[@]}"; do
104 $writesudo tee -a "$file"<<<"${strings[@]}"
105 done
106 fi
107 return 0
108 }