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

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