OILS / doc / ref / chap-cmd-lang.md View on Github | oils.pub

712 lines, 457 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### simple-command
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### prefix-binding
76
77Bindings are allowed before a simple command:
78
79 PYTHONPATH=. mydir/myscript.py
80
81These bindings set a variable and mark it exported. This binding is usually
82temporary, but when used with certain [special builtins][special], it persists.
83
84[special]: https://www.gnu.org/software/bash/manual/html_node/Special-Builtins.html
85
86- Related: [ysh-prefix-binding](ysh-prefix-binding)
87
88### ysh-prefix-binding
89
90YSH prefix bindings look exactly like they do in shell:
91
92 PYTHONPATH=. mydir/myscript.py
93
94However, they temporarily set `ENV.PYTHONPATH`, not `$PYTHONPATH`. This is
95done by adding a new `Dict` to the prototype chain of the `Obj`.
96
97The new `ENV` then becomes the environment of the child processes for the
98command.
99
100(In YSH, prefix bindings only mean one thing. They are temporary; they don't
101persist depending on whether the command is a special builtin.)
102
103- Related: [ENV](chap-special-var.html#ENV), [prefix-binding](chap-cmd-lang.html#prefix-binding)
104
105
106<h3 id="semicolon" class="osh-ysh-topic">semicolon ;</h3>
107
108Run two commands in sequence like this:
109
110 echo one; echo two
111
112or this:
113
114 echo one
115 echo two
116
117<h2 id="Conditional">Conditional</h2>
118
119<h3 id="case" class="osh-topic">case</h3>
120
121Match a string against a series of glob patterns. Execute code in the section
122below the matching pattern.
123
124 path='foo.py'
125 case "$path" in
126 *.py)
127 echo 'python'
128 ;;
129 *.sh)
130 echo 'shell'
131 ;;
132 esac
133
134For bash compatibility, the `;;` terminator can be substituted with either:
135
136- `;&` - fall through to next arm, ignoring the condition
137- `;;&` - fall through to next arm, respecting the condition
138
139<h3 id="if" class="osh-topic">if</h3>
140
141Test if a command exited with status zero (true). If so, execute the
142corresponding block of code.
143
144Shell:
145
146 if test -d foo; then
147 echo 'foo is a directory'
148 elif test -f foo; then
149 echo 'foo is a file'
150 else
151 echo 'neither'
152 fi
153
154YSH:
155
156 if test -d foo {
157 echo 'foo is a directory'
158 } elif test -f foo {
159 echo 'foo is a file'
160 } else {
161 echo 'neither'
162 }
163
164<h3 id="dbracket" class="osh-topic">dbracket [[</h3>
165
166Statically parsed boolean expressions, from bash and other shells:
167
168 x=42
169 if [[ $x -eq 42 ]]; then
170 echo yes
171 fi # => yes
172
173Compare with the [test][] builtin, which is dynamically parsed.
174
175See [bool-expr][] for the expression syntax.
176
177[test]: chap-builtin-cmd.html#test
178[bool-expr]: chap-mini-lang.html#bool-expr
179
180
181<h3 id="bang" class="osh-ysh-topic">bang !</h3>
182
183Invert an exit code:
184
185 if ! test -d /tmp; then
186 echo "No temp directory
187 fi
188
189<h3 id="and" class="osh-ysh-topic">and &amp;&amp;</h3>
190
191 mkdir -p /tmp && cp foo /tmp
192
193<h3 id="or" class="osh-ysh-topic">or ||</h3>
194
195 ls || die "failed"
196
197<h2 id="Iteration">Iteration</h2>
198
199<h3 id="while" class="osh-ysh-topic">while</h3>
200
201POSIX
202
203<h3 id="until" class="osh-topic">until</h3>
204
205POSIX
206
207<h3 id="for" class="osh-ysh-topic">for</h3>
208
209For loops iterate over words.
210
211YSH style:
212
213 var mystr = 'one'
214 var myarray = :| two three |
215
216 for i in $mystr @myarray *.py {
217 echo $i
218 }
219
220
221Shell style:
222
223 local mystr='one'
224 local myarray=(two three)
225
226 for i in "mystr" "${myarray[@]}" *.py; do
227 echo $i
228 done
229
230Both fragments output 3 lines and then Python files on remaining lines.
231
232<h3 id="for-expr-sh" class="osh-topic">for-expr-sh</h3>
233
234A bash/ksh construct:
235
236 for (( i = 0; i < 5; ++i )); do
237 echo $i
238 done
239
240<h2 id="Control Flow">Control Flow</h2>
241
242These are keywords in Oils, not builtins!
243
244### break
245
246Break out of a loop. (Not used for case statements!)
247
248### continue
249
250Continue to the next iteration of a loop.
251
252### return
253
254Return from a function.
255
256### exit
257
258Exit the shell process with the given status:
259
260 exit 2
261
262<h2 id="Grouping">Grouping</h2>
263
264### sh-func
265
266POSIX:
267
268 f() {
269 echo args "$@"
270 }
271 f 1 2 3
272
273### sh-block
274
275POSIX:
276
277 { echo one; echo two; }
278
279The trailing `;` is necessary in OSH, but not YSH. In YSH, `parse_brace` makes
280`}` is more of a special word.
281
282
283### subshell
284
285 ( echo one; echo two )
286
287In YSH, use [forkwait](chap-builtin-cmd.html#forkwait) instead of parentheses.
288
289<h2 id="Concurrency">Concurrency</h2>
290
291### pipe
292
293Pipelines are a traditional POSIX shell construct:
294
295 ls /tmp | grep ssh | sort
296
297Related:
298
299- [`PIPESTATUS`]() in OSH
300- [`_pipeline_status`]() in YSH
301
302[PIPESTATUS]: chap-special-var.html#PIPESTATUS
303[_pipeline_status]: chap-special-var.html#_pipeline_status
304
305<h3 id="ampersand" class="osh-topic">ampersand &amp;</h3>
306
307Start a command as a background job. Don't wait for it to finish, and return
308control to the shell.
309
310The PID of the job is recorded in the `$!` variable.
311
312 sleep 1 &
313 echo pid=$!
314 { echo two; sleep 2 } &
315 wait
316 wait
317
318In YSH, use the [fork][] builtin.
319
320[fork]: chap-builtin-cmd.html#fork
321
322
323<h2 id="Redirects">Redirects</h2>
324
325### redir-file
326
327The operators `>` and `>>` redirect the `stdout` of a process to a disk file.
328The `<` operator redirects `stdin` from a disk file.
329
330---
331
332Examples of redirecting the `stdout` of a command:
333
334 echo foo > out.txt # overwrite out.txt
335 date >> stamp.txt # append to stamp.txt
336
337<!--
338 echo foo >| out.txt # clobber the file even if set -o noclobber
339-->
340
341Redirect to the `stdin` of a command:
342
343 cat < in.txt
344
345Redirects are compatible with POSIX and bash, so they take descriptor numbers
346on the left:
347
348 make 2> stderr.txt # '2>' is valid, but '2 >' is not
349
350Note that the word argument to **file** redirects is evaluated like bash, which
351is different than other arguments to other redirects:
352
353 tar -x -z < Python* # glob must expand to exactly 1 file
354 tar -x -z < $myvar # $myvar is split because it's unquoted
355
356In other words, it's evaluated **as** a sequence of 1 word, which **produces**
357zero to N strings. But redirects are only valid when it produces exactly 1
358string.
359
360(Related: YSH uses `shopt --set simple_word_eval`, which means that globs that
361match nothing evaluate to zero strings, not themselves.)
362
363<!-- They also take a file descriptor on the left -->
364
365
366### redir-desc
367
368Redirect to a file descriptor:
369
370 echo 'to stderr' >&2
371
372<!--
373NOTE: >&2 is just like <&2
374There's no real difference.
375-->
376
377### here-doc
378
379Here documents let you write the `stdin` of a process in the shell program.
380
381Specify a delimiter word (like EOF) after the redir operator (like `<<`).
382
383If it's unquoted, then `$` expansion happens, like a double-quoted string:
384
385 cat <<EOF
386 here doc with $double ${quoted} substitution
387 EOF
388
389If the delimiter is quoted, then `$` expansion does **not** happen, like a
390single-quoted string:
391
392 cat <<'EOF'
393 price is $3.99
394 EOF
395
396Leading tabs can be stripped with the `<<-` operator:
397
398 myfunc() {
399 cat <<-EOF
400 here doc with one tab leading tab stripped
401 EOF
402 }
403
404### here-str
405
406The `<<<` operator means that the argument is a `stdin` string, not a
407chosen delimiter.
408
409 cat <<< 'here string'
410
411The string **plus a newline** is the `stdin` value, which is consistent with
412GNU bash.
413
414### ysh-here-str
415
416You can also use YSH multi-line strings as "here strings". For example:
417
418Double-quoted:
419
420 cat <<< """
421 double
422 quoted = $x
423 """
424
425Single-quoted:
426
427 cat <<< '''
428 price is
429 $3.99
430 '''
431
432J8-style with escapes:
433
434 cat <<< u'''
435 j8 style string price is
436 mu = \u{3bc}
437 '''
438
439In these cases, a trailing newline is **not** added. For example, the first
440example is equivalent to:
441
442 write --end '' -- """
443 double
444 quoted = $x
445 """
446
447## Other Command
448
449<h3 id="dparen" class="osh-topic">dparen ((</h3>
450
451<h3 id="time" class="osh-ysh-topic">time</h3>
452
453 time [-p] pipeline
454
455Measures the time taken by a command / pipeline. It uses the `getrusage()`
456function from `libc`.
457
458Note that time is a KEYWORD, not a builtin!
459
460<!-- Note: bash respects TIMEFORMAT -->
461
462
463## YSH Simple
464
465### typed-arg
466
467Internal commands (procs and builtins) accept typed arguments in parentheses:
468
469 json write (myobj)
470
471Redirects can also appear after the typed args:
472
473 json write (myobj) >out.txt
474
475### lazy-expr-arg
476
477Expressions in brackets like this:
478
479 assert [42 === x]
480
481Are syntactic sugar for:
482
483 assert (^[42 === x])
484
485That is, it's single arg of type `value.Expr`.
486
487Redirects can also appear after the lazy typed args:
488
489 assert [42 === x] >out.txt
490
491- Related: [Expr][] type
492
493[Expr]: chap-type-method.html#Expr
494
495### block-arg
496
497Blocks can be passed to simple commands, either literally:
498
499 cd /tmp {
500 echo $PWD # prints /tmp
501 }
502 echo $PWD
503
504Or as an expression:
505
506 var block = ^(echo $PWD)
507 cd /tmp (; ; block)
508
509Note that `cd` has no typed or named arguments, so the two semicolons are
510preceded by nothing.
511
512When passed to procs, blocks capture the enclosing stack frame:
513
514 var x = 42
515 myproc {
516 # lexical scope is respected
517 echo "x = $x" # x = 42
518 }
519
520---
521
522Redirects can appear after the block arg:
523
524 cd /tmp {
525 echo $PWD # prints /tmp
526 } >out.txt
527
528
529Related:
530
531- [sh-block](#sh-block) in OSH.
532- [Command][] and [CommandFrag][] types.
533
534[Command]: chap-type-method.html#Command
535[CommandFrag]: chap-type-method.html#CommandFrag
536
537## YSH Cond
538
539### ysh-case
540
541Like the shell case statement, the Ysh case statement has **string/glob** patterns.
542
543 var s = 'README.md'
544 case (s) {
545 *.py { echo 'Python' }
546 *.cc | *.h { echo 'C++' }
547 * { echo 'Other' }
548 }
549 # => Other
550
551We also generated it to **typed data** within `()`:
552
553 var x = 43
554 case (x) {
555 (30 + 12) { echo 'the integer 42' }
556 (else) { echo 'neither' }
557 }
558 # => neither
559
560The `else` is a special keyword that matches any value.
561
562 case (s) {
563 / dot* '.md' / { echo 'Markdown' }
564 (else) { echo 'neither' }
565 }
566 # => Markdown
567
568### ysh-if
569
570Like shell, you can use a command:
571
572 if test --file $x {
573 echo "$x is a file"
574 }
575
576You can also use an expression:
577
578 if (x > 0) {
579 echo 'positive'
580 }
581
582## YSH Iter
583
584### ysh-for
585
586#### Words
587
588This is a shell-style loop over "words":
589
590 for word in 'oils' $num_beans {pea,coco}nut {
591 echo $word
592 }
593 # =>
594 # oils
595 # 13
596 # peanut
597 # coconut
598
599You can ask for the loop index with `i,`:
600
601 for i, name in README.md *.py {
602 echo "$i $name"
603 }
604 # => 0 README.md
605 # => 1 foo.py
606
607#### Expressions Over Typed Data
608
609Expressions are enclosed in `()`. You can iterate over a `Range`, `List`,
610`Dict`, or `io.stdin`.
611
612Range:
613
614 for i in (3 ..< 5) { # range operator ..<
615 echo "i = $i"
616 }
617 # =>
618 # i = 3
619 # i = 4
620
621List:
622
623 var foods = ['ale', 'bean']
624 for item in (foods) {
625 echo $item
626 }
627 # =>
628 # ale
629 # bean
630
631---
632
633There are **three** ways of iterating over a `Dict`:
634
635 var mydict = {pea: 42, nut: 10}
636 for key in (mydict) {
637 echo $key
638 }
639 # =>
640 # pea
641 # nut
642
643 for key, value in (mydict) {
644 echo "$key $value"
645 }
646 # =>
647 # pea - 42
648 # nut - 10
649
650 for i, key, value in (mydict) {
651 echo "$i $key $value"
652 }
653 # =>
654 # 0 - pea - 42
655 # 1 - nut - 10
656
657That is, if you ask for two things, you'll get the key and value. If you ask
658for three, you'll also get the index.
659
660(One way to think of it: `for` loops in YSH have the functionality Python's
661`enumerate()`, `items()`, `keys()`, and `values()`.)
662
663---
664
665The `io.stdin` object iterates over lines:
666
667 for line in (io.stdin) {
668 echo $line
669 }
670 # lines are buffered, so it's much faster than `while read --raw-line`
671
672---
673
674(This section is based on [A Tour of YSH](../ysh-tour.html).)
675
676#### Closing Over the Loop Variable
677
678Each iteration of a `for` loop creates a new frame, which may be captured.
679
680 var x = 42 # outside the loop
681 for i in (0 ..< 3) {
682 var j = i + 2
683
684 var expr = ^"$x: i = $i, j = $j" # captures x, i, and j
685
686 my-task {
687 echo "$x: i = $i, j = $j" # also captures x, i, and j
688 }
689 }
690
691#### Mutating Containers in a `for` Loop
692
693- If you append or remove from a `List` while iterating over it, the loop **will** be affected.
694- If you mutate a `Dict` while iterating over it, the loop will **not** be
695 affected.
696
697### ysh-while
698
699You can use an expression as the condition:
700
701 var x = 5
702 while (x < 0) {
703 setvar x -= 1
704 }
705
706You or a command:
707
708 while test -f myfile {
709 echo 'myfile'
710 sleep 1
711 }
712