OILS / doc / ref / chap-cmd-lang.md View on Github | oilshell.org

573 lines, 364 significant
1---
2title: Command Language (Oils Reference)
3all_docs_url: ..
4body_css_class: width40
5default_highlighter: oils-sh
6preserve_anchor_case: yes
7---
8
9<div class="doc-ref-header">
10
11[Oils Reference](index.html) &mdash;
12Chapter **Command Language**
13
14</div>
15
16This chapter describes the command language for OSH, and some YSH extensions.
17
18<span class="in-progress">(in progress)</span>
19
20<div id="dense-toc">
21</div>
22
23## Quick Sketch: What's a Command?
24
25OSH:
26
27 print-files() {
28 for name in *.py; do
29 if test -x "$name"; then
30 echo "$name is executable"
31 fi
32 done
33 }
34
35YSH:
36
37 proc print-files {
38 for name in *.py {
39 if test -x $name { # no quotes needed
40 echo "$name is executable"
41 }
42 }
43 }
44
45
46<h2 id="Commands">Commands</h2>
47
48<h3 id="simple-command" class="osh-ysh-topic">simple-command</h3>
49
50Commands are composed of words. The first word may be the name of
51
521. A builtin shell command
531. A YSH `proc` or shell "function"
541. A Hay node declared with `hay define`
551. An external command
561. An alias
57
58Examples:
59
60 echo hi # a shell builtin doesn't start a process
61 ls /usr/bin ~/src # starts a new process
62 myproc "hello $name"
63 myshellfunc "hello $name"
64 myalias -l
65<!-- TODO: document lookup order -->
66
67Redirects are also allowed in any part of the command:
68
69 echo 'to stderr' >&2
70 echo >&2 'to stderr'
71
72 echo 'to file' > out.txt
73 echo > out.txt 'to file'
74
75<h3 id="semicolon" class="osh-ysh-topic">semicolon ;</h3>
76
77Run two commands in sequence like this:
78
79 echo one; echo two
80
81or this:
82
83 echo one
84 echo two
85
86<h2 id="Conditional">Conditional</h2>
87
88<h3 id="case" class="osh-topic">case</h3>
89
90Match a string against a series of glob patterns. Execute code in the section
91below the matching pattern.
92
93 path='foo.py'
94 case "$path" in
95 *.py)
96 echo 'python'
97 ;;
98 *.sh)
99 echo 'shell'
100 ;;
101 esac
102
103For bash compatibility, the `;;` terminator can be substituted with either:
104
105- `;&` - fall through to next arm, ignoring the condition
106- `;;&` - fall through to next arm, respecting the condition
107
108<h3 id="if" class="osh-topic">if</h3>
109
110Test if a command exited with status zero (true). If so, execute the
111corresponding block of code.
112
113Shell:
114
115 if test -d foo; then
116 echo 'foo is a directory'
117 elif test -f foo; then
118 echo 'foo is a file'
119 else
120 echo 'neither'
121 fi
122
123YSH:
124
125 if test -d foo {
126 echo 'foo is a directory'
127 } elif test -f foo {
128 echo 'foo is a file'
129 } else {
130 echo 'neither'
131 }
132
133<h3 id="dbracket" class="osh-topic">dbracket [[</h3>
134
135Statically parsed boolean expressions, from bash and other shells:
136
137 x=42
138 if [[ $x -eq 42 ]]; then
139 echo yes
140 fi # => yes
141
142Compare with the [test][] builtin, which is dynamically parsed.
143
144See [bool-expr][] for the expression syntax.
145
146[test]: chap-builtin-cmd.html#test
147[bool-expr]: chap-mini-lang.html#bool-expr
148
149
150<h3 id="true" class="osh-ysh-topic">true</h3>
151
152Do nothing and return status 0.
153
154 if true; then
155 echo hello
156 fi
157
158<h3 id="false" class="osh-ysh-topic">false</h3>
159
160Do nothing and return status 1.
161
162 if false; then
163 echo 'not reached'
164 else
165 echo hello
166 fi
167
168<h3 id="colon" class="osh-topic">colon :</h3>
169
170Like `true`: do nothing and return status 0.
171
172<h3 id="bang" class="osh-ysh-topic">bang !</h3>
173
174Invert an exit code:
175
176 if ! test -d /tmp; then
177 echo "No temp directory
178 fi
179
180<h3 id="and" class="osh-ysh-topic">and &amp;&amp;</h3>
181
182 mkdir -p /tmp && cp foo /tmp
183
184<h3 id="or" class="osh-ysh-topic">or ||</h3>
185
186 ls || die "failed"
187
188<h2 id="Iteration">Iteration</h2>
189
190<h3 id="while" class="osh-ysh-topic">while</h3>
191
192POSIX
193
194<h3 id="until" class="osh-topic">until</h3>
195
196POSIX
197
198<h3 id="for" class="osh-ysh-topic">for</h3>
199
200For loops iterate over words.
201
202YSH style:
203
204 var mystr = 'one'
205 var myarray = :| two three |
206
207 for i in $mystr @myarray *.py {
208 echo $i
209 }
210
211
212Shell style:
213
214 local mystr='one'
215 local myarray=(two three)
216
217 for i in "mystr" "${myarray[@]}" *.py; do
218 echo $i
219 done
220
221Both fragments output 3 lines and then Python files on remaining lines.
222
223<h3 id="for-expr-sh" class="osh-topic">for-expr-sh</h3>
224
225A bash/ksh construct:
226
227 for (( i = 0; i < 5; ++i )); do
228 echo $i
229 done
230
231<h2 id="Control Flow">Control Flow</h2>
232
233These are keywords in Oils, not builtins!
234
235### break
236
237Break out of a loop. (Not used for case statements!)
238
239### continue
240
241Continue to the next iteration of a loop.
242
243### return
244
245Return from a function.
246
247### exit
248
249Exit the shell process with the given status:
250
251 exit 2
252
253<h2 id="Grouping">Grouping</h2>
254
255### sh-func
256
257POSIX:
258
259 f() {
260 echo args "$@"
261 }
262 f 1 2 3
263
264### sh-block
265
266POSIX:
267
268 { echo one; echo two; }
269
270The trailing `;` is necessary in OSH, but not YSH. In YSH, `parse_brace` makes
271`}` is more of a special word.
272
273
274### subshell
275
276 ( echo one; echo two )
277
278In YSH, use [forkwait](chap-builtin-cmd.html#forkwait) instead of parentheses.
279
280<h2 id="Concurrency">Concurrency</h2>
281
282### pipe
283
284Pipelines are a traditional POSIX shell construct:
285
286 ls /tmp | grep ssh | sort
287
288Related:
289
290- [`PIPESTATUS`]() in OSH
291- [`_pipeline_status`]() in YSH
292
293[PIPESTATUS]: chap-special-var.html#PIPESTATUS
294[_pipeline_status]: chap-special-var.html#_pipeline_status
295
296<h3 id="ampersand" class="osh-topic">ampersand &amp;</h3>
297
298Start a command as a background job. Don't wait for it to finish, and return
299control to the shell.
300
301The PID of the job is recorded in the `$!` variable.
302
303 sleep 1 &
304 echo pid=$!
305 { echo two; sleep 2 } &
306 wait
307 wait
308
309In YSH, use the [fork][] builtin.
310
311[fork]: chap-builtin-cmd.html#fork
312
313
314<h2 id="Redirects">Redirects</h2>
315
316### redir-file
317
318Examples of redirecting the `stdout` of a command:
319
320 echo foo > out.txt # overwrite out.txt
321 date >> stamp.txt # append to stamp.txt
322
323<!--
324 echo foo >| out.txt # clobber the file even if set -o noclobber
325-->
326
327Redirect to the `stdin` of a command:
328
329 cat < in.txt
330
331Redirects are compatible with POSIX and bash, so they take descriptor numbers
332on the left:
333
334 make 2> stderr.txt # '2>' is valid, but '2 >' is not
335
336Note that the word argument to **file** redirects is evaluated like bash, which
337is different than other arguments to other redirects:
338
339 tar -x -z < Python* # glob must expand to exactly 1 file
340 tar -x -z < $myvar # $myvar is split because it's unquoted
341
342In other words, it's evaluated **as** a sequence of 1 word, which **produces**
343zero to N strings. But redirects are only valid when it produces exactly 1
344string.
345
346(Related: YSH uses `shopt --set simple_word_eval`, which means that globs that
347match nothing evaluate to zero strings, not themselves.)
348
349<!-- They also take a file descriptor on the left -->
350
351
352### redir-desc
353
354Redirect to a file descriptor:
355
356 echo 'to stderr' >&2
357
358<!--
359NOTE: >&2 is just like <&2
360There's no real difference.
361-->
362
363### here-doc
364
365TODO: unbalanced HTML if we use \<\<?
366
367 cat <<EOF
368 here doc with $double ${quoted} substitution
369 EOF
370
371 myfunc() {
372 cat <<-EOF
373 here doc with one tab leading tab stripped
374 EOF
375 }
376
377 cat <<< 'here string'
378
379<!-- TODO: delimiter can be quoted -->
380<!-- Note: Python's HTML parser thinks <EOF starts a tag -->
381
382## Other Command
383
384<h3 id="dparen" class="osh-topic">dparen ((</h3>
385
386<h3 id="time" class="osh-ysh-topic">time</h3>
387
388 time [-p] pipeline
389
390Measures the time taken by a command / pipeline. It uses the `getrusage()`
391function from `libc`.
392
393Note that time is a KEYWORD, not a builtin!
394
395<!-- Note: bash respects TIMEFORMAT -->
396
397
398## YSH Simple
399
400### typed-arg
401
402Internal commands (procs and builtins) accept typed arguments in parentheses:
403
404 json write (myobj)
405
406Redirects can also appear after the typed args:
407
408 json write (myobj) >out.txt
409
410### lazy-expr-arg
411
412Expressions in brackets like this:
413
414 assert [42 === x]
415
416Are syntactic sugar for:
417
418 assert (^[42 === x])
419
420That is, it's single arg of type `value.Expr`.
421
422Redirects can also appear after the lazy typed args:
423
424 assert [42 ===x] >out.txt
425
426### block-arg
427
428Blocks can be passed to simple commands, either literally:
429
430 cd /tmp {
431 echo $PWD # prints /tmp
432 }
433 echo $PWD
434
435Or as an expression:
436
437 var block = ^(echo $PWD)
438 cd /tmp (; ; block)
439
440Note that `cd` has no typed or named arguments, so the two semicolons are
441preceded by nothing.
442
443Compare with [sh-block](#sh-block).
444
445Redirects can appear after the block arg:
446
447 cd /tmp {
448 echo $PWD # prints /tmp
449 } >out.txt
450
451## YSH Cond
452
453### ysh-if
454
455Like shell, you can use a command:
456
457 if test --file $x {
458 echo "$x is a file"
459 }
460
461You can also use an expression:
462
463 if (x > 0) {
464 echo 'positive'
465 }
466
467### ysh-case
468
469Like the shell case statement, the Ysh case statement has **string/glob** patterns.
470
471 var s = 'README.md'
472 case (s) {
473 *.py { echo 'Python' }
474 *.cc | *.h { echo 'C++' }
475 * { echo 'Other' }
476 }
477 # => Other
478
479We also generated it to **typed data** within `()`:
480
481 var x = 43
482 case (x) {
483 (30 + 12) { echo 'the integer 42' }
484 (else) { echo 'neither' }
485 }
486 # => neither
487
488The `else` is a special keyword that matches any value.
489
490 case (s) {
491 / dot* '.md' / { echo 'Markdown' }
492 (else) { echo 'neither' }
493 }
494 # => Markdown
495
496## YSH Iter
497
498### ysh-while
499
500Command or expression:
501
502 var x = 5
503 while (x < 0) {
504 setvar x -= 1
505 }
506
507### ysh-for
508
509#### Words
510
511This is a shell-style loop over "words":
512
513 for name in README.md *.py {
514 echo $name
515 }
516 # => README.md
517 # => foo.py
518
519You can also ask for the index:
520
521 for i, name in README.md *.py {
522 echo "$i $name"
523 }
524 # => 0 README.md
525 # => 1 foo.py
526
527#### Lines of `stdin`
528
529Here's how to iterate over the lines of stdin:
530
531 for line in (stdin) {
532 echo $line
533 }
534
535Likewise, you can ask for the index with `for i, line in (stdin) { ...`.
536
537#### Expressions
538
539Expressions are enclosed in `()`.
540
541Iterating over a `List` or `Range` is like iterating over words or lines:
542
543 var mylist = [42, 43]
544 for item in (mylist) {
545 echo $item
546 }
547 # => 42
548 # => 43
549
550 var n = 5
551 for i in (3 .. n) {
552 echo $i
553 }
554 # => 3
555 # => 4
556
557However, there are **three** ways of iterating over a `Dict`:
558
559 for key in (mydict) {
560 echo $key
561 }
562
563 for key, value in (mydict) {
564 echo "$key $value"
565 }
566
567 for i, key, value in (mydict) {
568 echo "$i $key $value"
569 }
570
571That is, if you ask for two things, you'll get the key and value. If you ask
572for three, you'll also get the index.
573