├── app.yaml
├── app_engine_handlers.py
├── minifier.py
├── run_tests.sh
├── template.html
└── tests
├── minified_t10.sh
├── minified_t2.sh
├── minified_t3.sh
├── minified_t5.sh
├── minified_t7.sh
├── minified_t9.sh
├── minified_t_arithmetic_expansion.sh
├── minified_t_case.sh
├── minified_t_dot.sh
├── minified_t_escaped.sh
├── minified_t_functions.sh
├── minified_t_heredocs_and_herestrings.sh
├── minified_t_if_statement.sh
├── minified_t_line_continuation.sh
├── minified_t_parameters_expansion.sh
├── minified_t_pipe.sh
├── minified_t_process_substitution.sh
├── minified_t_while_until_loops.sh
├── t10.sh
├── t2.sh
├── t3.sh
├── t5.sh
├── t7.sh
├── t9.sh
├── t_arithmetic_expansion.sh
├── t_case.sh
├── t_dot.sh
├── t_escaped.sh
├── t_functions.sh
├── t_heredocs_and_herestrings.sh
├── t_if_statement.sh
├── t_line_continuation.sh
├── t_parameters_expansion.sh
├── t_pipe.sh
├── t_process_substitution.sh
└── t_while_until_loops.sh
/app.yaml:
--------------------------------------------------------------------------------
1 | #application: bash-minifier
2 | #version: 1
3 | runtime: python27
4 | api_version: 1
5 | threadsafe: true
6 |
7 | handlers:
8 | - url: /.*
9 | script: app_engine_handlers.application
10 |
--------------------------------------------------------------------------------
/app_engine_handlers.py:
--------------------------------------------------------------------------------
1 | import webapp2
2 | import cgi
3 | import urllib
4 | import minifier
5 |
6 | class MainPage(webapp2.RequestHandler):
7 |
8 | def get_template(self):
9 | template = ""
10 | with open("template.html", "r") as tfile:
11 | template = tfile.read()
12 | return template
13 |
14 | def get(self):
15 | url = self.request.get("url", None)
16 | self.response.headers["Content-Type"] = "text/html"
17 | if url:
18 | try:
19 | stream = urllib.urlopen(url)
20 | if stream.code != 200:
21 | raise ValueError
22 | user_source = stream.read()
23 | return self.returnMinifiedSource(user_source)
24 | except:
25 | self.response.set_status(400)
26 | return self.response.write("
400
Bad url")
27 | params = dict(user_source="", processed_source="")
28 | self.response.write(self.get_template() % params)
29 |
30 | def post(self):
31 | user_source = self.request.get("user_source", "")
32 | self.returnMinifiedSource(user_source)
33 |
34 | def returnMinifiedSource(self, user_source):
35 | processed_source = ""
36 | if user_source.strip():
37 | try:
38 | processed_source = minifier.minify(user_source.replace("\r",""))
39 | except Exception, e:
40 | processed_source = str(e)
41 | self.response.headers["Content-Type"] = "text/html"
42 | params = dict(user_source=cgi.escape(user_source),
43 | processed_source=cgi.escape(processed_source))
44 | self.response.write(self.get_template() % params)
45 |
46 |
47 |
48 | application = webapp2.WSGIApplication([
49 | ("/", MainPage),
50 | ])
51 |
52 |
53 |
--------------------------------------------------------------------------------
/minifier.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import sys
4 |
5 |
6 | class BashFileIterator:
7 | class _Delimiter(object):
8 | def __init__(self, character, _type=''):
9 | self.character = character
10 | # type may be 'AP' or 'AS' (Arithmetic Expansion delimited by (()) or [] respectively),
11 | # 'S' (Command Substitution) or 'P' (Parameter Expansion)
12 | # type is set only for parenthesis or curly brace and square brace that opens group
13 | # e.g. in this statement $((1+2)) only the 1st '(' will have type ('AP')
14 | self.type = _type
15 |
16 | def is_group_opening(self):
17 | return bool(self.type or self.character in ("'", '"', '`'))
18 |
19 | def __eq__(self, other):
20 | if isinstance(other, BashFileIterator._Delimiter):
21 | return other.character == self.character
22 | elif isinstance(other, basestring):
23 | return other == self.character
24 | return False
25 |
26 | def __ne__(self, other):
27 | return not self.__eq__(other)
28 |
29 | def __str__(self):
30 | return self.character
31 |
32 | __repr__ = __str__
33 |
34 | def __init__(self, src):
35 | self.src = src
36 | self.reset()
37 |
38 | def reset(self):
39 | self.pos = 0
40 | self.insideComment = False
41 | self.insideHereDoc = False
42 |
43 | # possible characters in stack:
44 | # (, ) -- means Arithmetic Expansion or Command Substitution
45 | # {, } -- means Parameter Expansion
46 | # [, ] -- means Arithmetic Expansion
47 | # ` -- means Command Substitution
48 | # ' -- means single-quoted string
49 | # " -- means double-quoted string
50 | self._delimiters_stack = []
51 | self._indices_of_escaped_characters = set()
52 |
53 | def getLastGroupOpeningDelimiter(self):
54 | return next((d for d in reversed(self._delimiters_stack) if d.is_group_opening()),
55 | BashFileIterator._Delimiter(''))
56 |
57 | def pushDelimiter(self, character, _type=''):
58 | d = BashFileIterator._Delimiter(character, _type=_type)
59 | last_opening = self.getLastGroupOpeningDelimiter()
60 | last = self._delimiters_stack[-1] if len(self._delimiters_stack) > 0 else BashFileIterator._Delimiter('')
61 |
62 | if d in ('{', '}'):
63 | if _type != '': # delimiter that opens group
64 | self._delimiters_stack.append(d)
65 | elif d == '}' and last == '{':
66 | self._delimiters_stack.pop()
67 | elif d in ('(', ')'):
68 | if _type != '': # delimiter that opens group
69 | self._delimiters_stack.append(d)
70 | elif last_opening == '(':
71 | if last == '(' and d == ')':
72 | self._delimiters_stack.pop()
73 | else:
74 | self._delimiters_stack.append(d)
75 | elif d in ('[', ']'):
76 | if _type != '': # delimiter that opens group
77 | self._delimiters_stack.append(d)
78 | elif last_opening == '[':
79 | if last == '[' and d == ']':
80 | self._delimiters_stack.pop()
81 | else:
82 | self._delimiters_stack.append(d)
83 | elif d == "'" and last_opening != '"' or d == '"' and last_opening != "'" or d == '`':
84 | if d == last_opening:
85 | self._delimiters_stack.pop()
86 | else:
87 | self._delimiters_stack.append(d)
88 |
89 | def isInsideGroup(self):
90 | return len(self._delimiters_stack) != 0
91 |
92 | def getPreviousCharacters(self, n, should_not_start_with_escaped=True):
93 | """
94 | 'should_not_start_with_escaped' means return empty string if the first character is escaped
95 | """
96 | first_character_index = max(0, self.pos - n)
97 | if first_character_index in self._indices_of_escaped_characters:
98 | return ''
99 | else:
100 | return self.src[max(0, self.pos - n):self.pos]
101 |
102 | def getPreviousCharacter(self, should_not_start_with_escaped=True):
103 | return self.getPreviousCharacters(1, should_not_start_with_escaped=should_not_start_with_escaped)
104 |
105 | def getNextCharacters(self, n):
106 | return self.src[self.pos + 1:self.pos + n + 1]
107 |
108 | def getNextCharacter(self):
109 | return self.getNextCharacters(1)
110 |
111 | def getPreviousWord(self):
112 | word = ''
113 | i = 1
114 | while i <= self.pos:
115 | newWord = self.getPreviousCharacters(i)
116 | if not newWord.isalpha():
117 | break
118 | word = newWord
119 | i += 1
120 | return word
121 |
122 | def getNextWord(self):
123 | word = ''
124 | i = 1
125 | while self.pos + i < len(self.src):
126 | newWord = self.getNextCharacters(i)
127 | if not newWord.isalpha():
128 | break
129 | word = newWord
130 | i += 1
131 | return word
132 |
133 | def getPartOfLineAfterPos(self, skip=0):
134 | result = ''
135 | i = self.pos + 1 + skip
136 | while i < len(self.src) and self.src[i] != '\n':
137 | result += self.src[i]
138 | i += 1
139 | return result
140 |
141 | def getPartOfLineBeforePos(self, skip=0):
142 | result = ''
143 | i = self.pos - 1 - skip
144 | while i >= 0 and self.src[i] != '\n':
145 | result = self.src[i] + result
146 | i -= 1
147 | return result
148 |
149 | def charactersGenerator(self):
150 | hereDocWord = ''
151 | _yieldNextNCharactersAsIs = 0
152 |
153 | def close_heredoc():
154 | self.insideHereDoc = False
155 |
156 | callbacks_after_yield = []
157 |
158 | while self.pos < len(self.src):
159 | ch = self.src[self.pos]
160 |
161 | if _yieldNextNCharactersAsIs > 0:
162 | _yieldNextNCharactersAsIs -= 1
163 | elif ch == "\\" and not self.isEscaped():
164 | self._indices_of_escaped_characters.add(self.pos + 1)
165 | else:
166 | if ch == "\n" and not self.isInsideSingleQuotedString() and not self.isInsideDoubleQuotedString():
167 | # handle end of comments and heredocs
168 | if self.insideComment:
169 | self.insideComment = False
170 | elif self.insideHereDoc and self.getPartOfLineBeforePos() == hereDocWord:
171 | callbacks_after_yield.append(close_heredoc)
172 | elif not self.isInsideComment() and not self.isInsideHereDoc():
173 | if ch in ('"', "'"):
174 | # single quote can't be escaped inside single-quoted string
175 | if not self.isEscaped() or ch == "'" and self.isInsideSingleQuotedString():
176 | self.pushDelimiter(ch)
177 | elif not self.isInsideSingleQuotedString():
178 | if not self.isEscaped():
179 | if ch == "#" and not self.isInsideGroup() and \
180 | (self.getPreviousCharacter() in ('\n', '\t', ' ', ';') or self.pos == 0):
181 | # handle comments
182 | self.insideComment = True
183 | elif ch == '`':
184 | self.pushDelimiter(ch)
185 | elif ch == '$':
186 | next_char = self.getNextCharacter()
187 | if next_char in ('{', '(', '['):
188 | next_2_chars = self.getNextCharacters(2)
189 | _type = 'AP' if next_2_chars == '((' else {'{': 'P', '(': 'S', '[': 'AS'}[next_char]
190 | self.pushDelimiter(next_char, _type=_type)
191 | _yieldNextNCharactersAsIs = 1
192 | elif ch in ('{', '}', '(', ')', '[', ']'):
193 | self.pushDelimiter(ch)
194 | elif ch == '<' and self.getNextCharacter() == '<' and not self.isInsideGroup():
195 | _yieldNextNCharactersAsIs = 1
196 |
197 | # we should handle correctly heredocs and herestrings like this one:
198 | # echo <<< one
199 |
200 | if self.getNextCharacters(2) != '<<':
201 | # heredoc
202 | self.insideHereDoc = True
203 | hereDocWord = self.getPartOfLineAfterPos(skip=1)
204 | if hereDocWord[0] == '-':
205 | hereDocWord = hereDocWord[1:]
206 | hereDocWord = hereDocWord.strip().replace('"', '').replace("'", '')
207 |
208 | yield ch
209 |
210 | while len(callbacks_after_yield) > 0:
211 | callbacks_after_yield.pop()()
212 |
213 | self.pos += 1
214 |
215 | assert not self.isInsideGroup(), 'Invalid syntax'
216 | raise StopIteration
217 |
218 | def isEscaped(self):
219 | return self.pos in self._indices_of_escaped_characters
220 |
221 | def isInsideDoubleQuotedString(self):
222 | return self.getLastGroupOpeningDelimiter() == '"'
223 |
224 | def isInsideSingleQuotedString(self):
225 | return self.getLastGroupOpeningDelimiter() == "'"
226 |
227 | def isInsideComment(self):
228 | return self.insideComment
229 |
230 | def isInsideHereDoc(self):
231 | return self.insideHereDoc
232 |
233 | def isInsideParameterExpansion(self):
234 | return self.getLastGroupOpeningDelimiter() == '{'
235 |
236 | def isInsideArithmeticExpansion(self):
237 | return self.getLastGroupOpeningDelimiter().type in ('AP', 'AS')
238 |
239 | def isInsideCommandSubstitution(self):
240 | last_opening_delimiter = self.getLastGroupOpeningDelimiter()
241 | return last_opening_delimiter == '`' or last_opening_delimiter.type == 'S'
242 |
243 | def isInsideAnything(self):
244 | return self.isInsideGroup() or self.insideHereDoc or self.insideComment
245 |
246 | def isInsideGroupWhereWhitespacesCannotBeTruncated(self):
247 | return self.isInsideComment() or self.isInsideDoubleQuotedString() or self.isInsideDoubleQuotedString() or \
248 | self.isInsideHereDoc() or self.isInsideParameterExpansion()
249 |
250 |
251 | def minify(src):
252 | # first: remove all comments
253 | it = BashFileIterator(src)
254 | src = "" # result
255 | for ch in it.charactersGenerator():
256 | if not it.isInsideComment():
257 | src += ch
258 |
259 | # secondly: remove empty strings, strip lines and truncate spaces (replace groups of whitespaces by single space)
260 | it = BashFileIterator(src)
261 | src = "" # result
262 | emptyLine = True # means that no characters has been printed in current line so far
263 | previousSpacePrinted = True
264 | for ch in it.charactersGenerator():
265 | if it.isInsideSingleQuotedString():
266 | # first of all check single quoted string because line continuation does not work inside
267 | src += ch
268 | elif ch == "\\" and not it.isEscaped() and it.getNextCharacter() == "\n":
269 | # then check line continuation
270 | # line continuation will occur on the next iteration. just skip this backslash
271 | continue
272 | elif ch == "\n" and it.isEscaped():
273 | # line continuation occurred
274 | # backslash at the very end of line means line continuation
275 | # so remove previous backslash and skip current newline character ch
276 | continue
277 | elif it.isInsideGroupWhereWhitespacesCannotBeTruncated() or it.isEscaped():
278 | src += ch
279 | elif ch in (' ', '\t') and not previousSpacePrinted and not emptyLine and \
280 | not it.getNextCharacter() in (' ', '\t', '\n'):
281 | src += " "
282 | previousSpacePrinted = True
283 | elif ch == "\n" and it.getPreviousCharacter() != "\n" and not emptyLine:
284 | src += ch
285 | previousSpacePrinted = True
286 | emptyLine = True
287 | elif ch not in (' ', '\t', '\n'):
288 | src += ch
289 | previousSpacePrinted = False
290 | emptyLine = False
291 |
292 | # thirdly: get rid of newlines
293 | it = BashFileIterator(src)
294 | src = "" # result
295 | for ch in it.charactersGenerator():
296 | if it.isInsideAnything() or ch != "\n":
297 | src += ch
298 | else:
299 | prevWord = it.getPreviousWord()
300 | nextWord = it.getNextWord()
301 | if it.getNextCharacter() == '{': # functions declaration, see test t8.sh
302 | if it.getPreviousCharacter() == ')':
303 | continue
304 | else:
305 | src += ' '
306 | elif prevWord in ("until", "while", "then", "do", "else", "in", "elif", "if") or \
307 | nextWord in ("in",) or \
308 | it.getPreviousCharacter() in ("{", "(") or \
309 | it.getPreviousCharacters(2) in ("&&", "||"):
310 | src += " "
311 | elif nextWord in ("esac",) and it.getPreviousCharacters(2) != ';;':
312 | if it.getPreviousCharacter() == ';':
313 | src += ';'
314 | else:
315 | src += ';;'
316 | elif it.getNextCharacter() != "" and it.getPreviousCharacter() not in (";", '|'):
317 | src += ";"
318 |
319 | # finally: remove spaces around semicolons and pipes and other delimiters
320 | it = BashFileIterator(src)
321 | src = "" # result
322 | other_delimiters = ('|', '&', ';', '<', '>', '(', ')') # characters that may not be surrounded by whitespaces
323 | for ch in it.charactersGenerator():
324 | if it.isInsideGroupWhereWhitespacesCannotBeTruncated():
325 | src += ch
326 | elif ch in (' ', '\t') \
327 | and (it.getPreviousCharacter() in other_delimiters or
328 | it.getNextCharacter() in other_delimiters) \
329 | and it.getNextCharacters(2) not in ('<(', '>('): # process substitution
330 | # see test t_process_substitution.sh for details
331 | continue
332 | else:
333 | src += ch
334 |
335 | return src
336 |
337 |
338 | if __name__ == "__main__":
339 | # https://www.gnu.org/software/bash/manual/html_node/Reserved-Word-Index.html
340 | # http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
341 | # http://pubs.opengroup.org/onlinepubs/9699919799/
342 |
343 | # get bash source from file or from stdin
344 | src = ""
345 | if len(sys.argv) > 1:
346 | with open(sys.argv[1], "r") as ifile:
347 | src = ifile.read()
348 | else:
349 | src = sys.stdin.read()
350 | # use stdout.write instead of print to avoid newline at the end (print with comma at the end does not work)
351 | sys.stdout.write(minify(src))
352 |
353 |
354 | # important rules:
355 | # 1. A single-quote cannot occur within single-quotes.
356 | # 2. The input characters within the double-quoted string that are also enclosed between "$(" and the matching ')'
357 | # shall not be affected by the double-quotes, but rather shall define that command whose output replaces the "$(...)"
358 | # when the word is expanded.
359 | # 3. Within the double-quoted string of characters from an enclosed "${" to the matching '}', an even number of
360 | # unescaped double-quotes or single-quotes, if any, shall occur. A preceding character shall be used
361 | # to escape a literal '{' or '}'
362 | # 4.
363 |
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | pushd tests > /dev/null
4 | passed=0
5 | failed=0
6 | for f in t*.sh; do
7 | echo -n "$f "
8 | python ../minifier.py $f > _tmp_$f
9 | if [ `diff _tmp_$f minified_$f | wc -c` -ne 0 ]; then
10 | failed=$((failed+1))
11 | echo FAILED
12 | else
13 | passed=$((passed+1))
14 | echo passed
15 | fi
16 | rm _tmp_$f
17 | done
18 | echo PASSED $passed, FAILED $failed
19 | popd > /dev/null
--------------------------------------------------------------------------------
/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Bash Minifier
4 |
5 |
10 |
11 |
12 |
13 | Bash Scripts Minifier
14 |
19 |
20 |
21 |
22 | please report problems to vs.kulaga (at) gmail.com
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/minified_t10.sh:
--------------------------------------------------------------------------------
1 | echo " 1 '2 '3 '4 5 ";echo " ( ( ) ";echo " { { } ";echo " { ' { ' } ' "
--------------------------------------------------------------------------------
/tests/minified_t2.sh:
--------------------------------------------------------------------------------
1 | tname=${tname#*.}
--------------------------------------------------------------------------------
/tests/minified_t3.sh:
--------------------------------------------------------------------------------
1 | if [ $# -ne 1 ];then printf "Filename is required.\n";:;fi;echo "hi | hello `whoami|tr a-z A-Z`!"
--------------------------------------------------------------------------------
/tests/minified_t5.sh:
--------------------------------------------------------------------------------
1 | echo 1#2;echo 1##2;echo ${#}#c;echo 1 \# 2;echo \#1
--------------------------------------------------------------------------------
/tests/minified_t7.sh:
--------------------------------------------------------------------------------
1 | isql<&2;}>./xx$(date +'%T').txt;xx;e()(var0=20;echo 'i am "e" func';);e;echo var0=$var0;function no_braces()if true;then echo 'i am func without braces';fi;no_braces
--------------------------------------------------------------------------------
/tests/minified_t_heredocs_and_herestrings.sh:
--------------------------------------------------------------------------------
1 | cat<< EOF
2 |
3 | Working dir "$PWD" `pwd`
4 |
5 |
6 | line 2
7 |
8 | EOF
9 | ls /home/;cat<< EOF
10 |
11 | line 1
12 |
13 |
14 | Working dir "$PWD" `pwd`
15 |
16 | EOF
17 | echo $((1<<5));echo 1;echo 2;tr a-z A-Z<< END_TEXT
18 | one two three
19 | four five six
20 | END_TEXT
21 | tr a-z A-Z<<- END_TEXT
22 | one two three
23 | four five six
24 | END_TEXT
25 | cat<< EOF
26 | Working dir "$PWD" `pwd`
27 | EOF
28 | cat<< 'EOF'
29 | Working dir "$PWD" `pwd`
30 | EOF
31 | tr a-z A-Z<<(echo "# of lines:" `wc -l` 1>&2)|awk '{print $1*2}'
--------------------------------------------------------------------------------
/tests/minified_t_while_until_loops.sh:
--------------------------------------------------------------------------------
1 | echo "while";counter=0;while [ "$counter" -lt 10 ];do echo $counter;counter=$((counter+2));done;echo "until";counter=8;until [ "$counter" -lt 0 ];do echo $counter;counter=$((counter-2));done
--------------------------------------------------------------------------------
/tests/t10.sh:
--------------------------------------------------------------------------------
1 | echo " 1 '2 '3 '4 5 "
2 |
3 | echo " ( ( ) "
4 |
5 | echo " { { } "
6 |
7 | echo " { ' { ' } ' "
8 |
--------------------------------------------------------------------------------
/tests/t2.sh:
--------------------------------------------------------------------------------
1 | tname=${tname#*.}
2 |
--------------------------------------------------------------------------------
/tests/t3.sh:
--------------------------------------------------------------------------------
1 | if [ $# -ne 1 ]; then
2 | printf "Filename is required.\n"
3 | :
4 | fi
5 |
6 | echo "hi | hello `whoami | tr a-z A-Z`!"
7 |
--------------------------------------------------------------------------------
/tests/t5.sh:
--------------------------------------------------------------------------------
1 | # 1
2 | echo 1#2 #3
3 | echo 1##2 #
4 | echo ${#}#c
5 |
6 | echo 1 \# 2
7 |
8 | echo \#1
9 |
10 | # 2
--------------------------------------------------------------------------------
/tests/t7.sh:
--------------------------------------------------------------------------------
1 | isql <&2
74 | } > ./xx$(date +'%T').txt
75 |
76 | xx
77 |
78 | # function that invokes subshell
79 | e () (
80 | var0=20
81 | echo 'i am "e" func'
82 | )
83 |
84 | e
85 |
86 | echo var0=$var0 # should be empty
87 |
88 | # single command function
89 |
90 | function no_braces () if true
91 | then
92 | echo 'i am func without braces';
93 | fi
94 |
95 | no_braces
96 |
--------------------------------------------------------------------------------
/tests/t_heredocs_and_herestrings.sh:
--------------------------------------------------------------------------------
1 | # https://en.wikipedia.org/wiki/Here_document#Unix_shells
2 |
3 | cat << EOF
4 |
5 | Working dir "$PWD" `pwd`
6 |
7 |
8 | line 2
9 |
10 | EOF
11 | ls /home/
12 |
13 | cat << EOF
14 |
15 | line 1
16 |
17 |
18 | Working dir "$PWD" `pwd`
19 |
20 | EOF
21 |
22 | echo $((1<<5))
23 |
24 | echo 1
25 |
26 | echo 2
27 |
28 | tr a-z A-Z << END_TEXT
29 | one two three
30 | four five six
31 | END_TEXT
32 |
33 | tr a-z A-Z <<- END_TEXT
34 | one two three
35 | four five six
36 | END_TEXT
37 |
38 | cat << EOF
39 | Working dir "$PWD" `pwd`
40 | EOF
41 |
42 | cat << 'EOF'
43 | Working dir "$PWD" `pwd`
44 | EOF
45 |
46 | # HERESTRINGS
47 |
48 | tr a-z A-Z <<< one
49 |
50 | tr a-z A-Z <<< 'one two three'
51 |
52 | FOO='one two three'
53 | tr a-z A-Z <<< $FOO
54 |
55 | tr a-z A-Z <<< 'one
56 | two three'
57 |
58 | tr a-z A-Z <<< '
59 | one
60 | two three
61 | '
62 |
63 | tr a-z A-Z <<< "
64 | one
65 | two three
66 | "
67 |
68 | bc <<< 2^10
--------------------------------------------------------------------------------
/tests/t_if_statement.sh:
--------------------------------------------------------------------------------
1 | if
2 |
3 | [ "a" = "b" ]
4 |
5 | then
6 | echo 1
7 | elif
8 |
9 | [ "a" = "c" ]
10 |
11 | then
12 | echo 2
13 |
14 | elif [ "a" = "d" ];then
15 | echo 3
16 |
17 | else
18 |
19 | echo 4
20 |
21 | fi
--------------------------------------------------------------------------------
/tests/t_line_continuation.sh:
--------------------------------------------------------------------------------
1 | # line continuation
2 | # it does not work inside comments and single quoted strings
3 |
4 | # all next 4 echos should look identically after minification
5 | echo "ab" "cd"
6 |
7 | echo "ab" \
8 | "cd"
9 |
10 | echo "ab"\
11 | "cd"
12 |
13 | echo "ab" \
14 | "cd"
15 |
16 | echo "double quoted:"
17 | echo "ab\
18 | cd"
19 |
20 | echo "single quoted:"
21 | echo 'ab\
22 | cd'
23 |
24 | echo "parameter expansion \${}:"
25 | a=abcd
26 | echo ${a\
27 | }
28 |
29 | echo "command substitution \$():"
30 | echo $(echo a\
31 | b c d)
32 |
33 | echo "command substitution \`\`:"
34 | echo `echo a\
35 | b c d`
36 |
37 | echo "heredoc:"
38 | cat << HD
39 | HI!\
40 | I'm here doc
41 | HD
42 |
43 |
44 | echo "arithmetic expansion \$(()):"
45 | echo 15 \* 99 = $((15\
46 | *99))
47 |
48 | echo "comment:"
49 | # echo "if you THIS -- next statement is incorrect:";\
50 | echo "line continuation does not work inside comments"
--------------------------------------------------------------------------------
/tests/t_parameters_expansion.sh:
--------------------------------------------------------------------------------
1 | a=abc{def
2 | echo ${a#a*{}
3 |
4 | echo $( echo 11 )
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/t_pipe.sh:
--------------------------------------------------------------------------------
1 | seq 1 100 |
2 | grep 9
--------------------------------------------------------------------------------
/tests/t_process_substitution.sh:
--------------------------------------------------------------------------------
1 | # for details see
2 | # https://superuser.com/questions/1059781/what-exactly-is-in-bash-and-in-zsh
3 | # https://en.wikipedia.org/wiki/Process_substitution
4 |
5 | cat < <(which wc)
6 |
7 | cat <(which wc)
8 |
9 | seq 1 10 | tee >(echo "# of lines:" `wc -l` 1>&2) | awk '{print $1*2}'
10 |
11 |
--------------------------------------------------------------------------------
/tests/t_while_until_loops.sh:
--------------------------------------------------------------------------------
1 | echo "while"
2 |
3 | counter=0
4 |
5 | while
6 |
7 | [ "$counter" -lt 10 ]
8 |
9 | do
10 |
11 |
12 | echo $counter
13 |
14 |
15 | counter=$((counter+2))
16 |
17 | done
18 |
19 | echo "until"
20 |
21 |
22 | counter=8
23 |
24 | until
25 |
26 | [ "$counter" -lt 0 ]
27 |
28 | do
29 |
30 |
31 | echo $counter
32 |
33 |
34 | counter=$((counter-2))
35 |
36 | done
--------------------------------------------------------------------------------