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

612 lines, 380 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.
359
360 json write (myobj)
361
362Block literals have a special syntax:
363
364 cd /tmp {
365 echo $PWD
366 }
367
368This is equivalent to:
369
370 var cmd = ^(echo $PWD) # unevaluated command
371
372 cd /tmp (cmd) # pass typed arg
373
374### lazy-expr-arg
375
376Expressions in brackets like this:
377
378 assert [42 === x]
379
380Are syntactic sugar for:
381
382 assert (^[42 === x])
383
384That is, it's single arg of type `value.Expr`.
385
386### block-arg
387
388Blocks can be passed to builtins (and procs eventually):
389
390 cd /tmp {
391 echo $PWD # prints /tmp
392 }
393 echo $PWD
394
395Compare with [sh-block]($osh-help).
396
397
398## YSH Assign
399
400### const
401
402Binds a name to a YSH expression on the right, with a **dynamic** check to
403prevent mutation.
404
405 const c = 'mystr' # equivalent to readonly c=mystr
406 const pat = / digit+ / # an eggex, with no shell equivalent
407
408If you try to re-declare or mutate the name, the shell will fail with a runtime
409error. `const` uses the same mechanism as the `readonly` builtin.
410
411Consts should only appear at the top-level, and can't appear within `proc` or
412`func`.
413
414### var
415
416Initializes a name to a YSH expression.
417
418 var s = 'mystr' # equivalent to declare s=mystr
419 var pat = / digit+ / # an eggex, with no shell equivalent
420
421It's either global or scoped to the current function.
422
423You can bind multiple variables:
424
425 var flag, i = parseArgs(spec, ARGV)
426
427 var x, y = 42, 43
428
429You can omit the right-hand side:
430
431 var x, y # implicitly initialized to null
432
433### setvar
434
435At the top-level, setvar creates or mutates a variable.
436
437 setvar gFoo = 'mutable'
438
439Inside a func or proc, it mutates a local variable declared with var.
440
441 proc p {
442 var x = 42
443 setvar x = 43
444 }
445
446You can mutate a List location:
447
448 setvar a[42] = 'foo'
449
450Or a Dict location:
451
452 setvar d['key'] = 43
453 setvar d.key = 43 # same thing
454
455You can use any of these these augmented assignment operators
456
457 += -= *= /= **= //= %=
458 &= |= ^= <<= >>=
459
460Examples:
461
462 setvar x += 2 # increment by 2
463
464 setvar a[42] *= 2 # multiply by 2
465
466 setvar d.flags |= 0b0010_000 # set a flag
467
468
469### setglobal
470
471Creates or mutates a global variable. Has the same syntax as `setvar`.
472
473
474## YSH Expr
475
476### equal
477
478The `=` keyword evaluates an expression and shows the result:
479
480 oil$ = 1 + 2*3
481 (Int) 7
482
483It's meant to be used interactively. Think of it as an assignment with no
484variable on the left.
485
486### call
487
488The `call` keyword evaluates an expression and throws away the result:
489
490 var x = :| one two |
491 call x->append('three')
492 call x->append(['typed', 'data'])
493
494
495## YSH Code
496
497### proc-def
498
499Procs are shell-like functions, but with named parameters, and without dynamic
500scope (TODO):
501
502 proc copy(src, dest) {
503 cp --verbose --verbose $src $dest
504 }
505
506Compare with [sh-func]($osh-help).
507
508### func-def
509
510TODO
511
512### ysh-return
513
514To return an expression, wrap it in `()` as usual:
515
516 func inc(x) {
517 return (x + 1)
518 }
519
520## YSH Cond
521
522### ysh-if
523
524Like shell, you can use a command:
525
526 if test --file $x {
527 echo "$x is a file"
528 }
529
530You can also use an expression:
531
532 if (x > 0) {
533 echo 'positive'
534 }
535
536### ysh-case
537
538Like the shell case statement, the Ysh case statement has **string/glob** patterns.
539
540 var s = 'README.md'
541 case (s) {
542 *.py { echo 'Python' }
543 *.cc | *.h { echo 'C++' }
544 * { echo 'Other' }
545 }
546 # => Other
547
548We also generated it to **typed data** within `()`:
549
550 var x = 43
551 case (x) {
552 (30 + 12) { echo 'the integer 42' }
553 (else) { echo 'neither' }
554 }
555 # => neither
556
557The `else` is a special keyword that matches any value.
558
559 case (s) {
560 / dot* '.md' / { echo 'Markdown' }
561 (else) { echo 'neither' }
562 }
563 # => Markdown
564
565## YSH Iter
566
567### ysh-while
568
569Command or expression:
570
571 var x = 5
572 while (x < 0) {
573 setvar x -= 1
574 }
575
576### ysh-for
577
578Two forms for shell-style loops:
579
580 for name in *.py {
581 echo "$name"
582 }
583
584 for i, name in *.py {
585 echo "$i $name"
586 }
587
588Two forms for expressions that evaluate to a `List`:
589
590 for item in (mylist) {
591 echo "$item"
592 }
593
594 for i, item in (mylist) {
595 echo "$i $item"
596 }
597
598Three forms for expressions that evaluate to a `Dict`:
599
600 for key in (mydict) {
601 echo "$key"
602 }
603
604 for key, value in (mydict) {
605 echo "$key $value"
606 }
607
608 for i, key, value in (mydict) {
609 echo "$i $key $value"
610 }
611
612# vim: sw=2