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

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