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

636 lines, 406 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
318The operators `>` and `>>` redirect the `stdout` of a process to a disk file.
319The `<` operator redirects `stdin` from a disk file.
320
321---
322
323Examples of redirecting the `stdout` of a command:
324
325 echo foo > out.txt # overwrite out.txt
326 date >> stamp.txt # append to stamp.txt
327
328<!--
329 echo foo >| out.txt # clobber the file even if set -o noclobber
330-->
331
332Redirect to the `stdin` of a command:
333
334 cat < in.txt
335
336Redirects are compatible with POSIX and bash, so they take descriptor numbers
337on the left:
338
339 make 2> stderr.txt # '2>' is valid, but '2 >' is not
340
341Note that the word argument to **file** redirects is evaluated like bash, which
342is different than other arguments to other redirects:
343
344 tar -x -z < Python* # glob must expand to exactly 1 file
345 tar -x -z < $myvar # $myvar is split because it's unquoted
346
347In other words, it's evaluated **as** a sequence of 1 word, which **produces**
348zero to N strings. But redirects are only valid when it produces exactly 1
349string.
350
351(Related: YSH uses `shopt --set simple_word_eval`, which means that globs that
352match nothing evaluate to zero strings, not themselves.)
353
354<!-- They also take a file descriptor on the left -->
355
356
357### redir-desc
358
359Redirect to a file descriptor:
360
361 echo 'to stderr' >&2
362
363<!--
364NOTE: >&2 is just like <&2
365There's no real difference.
366-->
367
368### here-doc
369
370Here documents let you write the `stdin` of a process in the shell program.
371
372Specify a delimiter word (like EOF) after the redir operator (like `<<`).
373
374If it's unquoted, then `$` expansion happens, like a double-quoted string:
375
376 cat <<EOF
377 here doc with $double ${quoted} substitution
378 EOF
379
380If the delimiter is quoted, then `$` expansion does **not** happen, like a
381single-quoted string:
382
383 cat <<'EOF'
384 price is $3.99
385 EOF
386
387Leading tabs can be stripped with the `<<-` operator:
388
389 myfunc() {
390 cat <<-EOF
391 here doc with one tab leading tab stripped
392 EOF
393 }
394
395### here-str
396
397The `<<<` operator means that the argument is a `stdin` string, not a
398chosen delimiter.
399
400 cat <<< 'here string'
401
402The string **plus a newline** is the `stdin` value, which is consistent with
403GNU bash.
404
405### ysh-here-str
406
407You can also use YSH multi-line strings as "here strings". For example:
408
409Double-quoted:
410
411 cat <<< """
412 double
413 quoted = $x
414 """
415
416Single-quoted:
417
418 cat <<< '''
419 price is
420 $3.99
421 '''
422
423J8-style with escapes:
424
425 cat <<< u'''
426 j8 style string price is
427 mu = \u{3bc}
428 '''
429
430In these cases, a trailing newline is **not** added. For example, the first
431example is equivalent to:
432
433 write --end '' -- """
434 double
435 quoted = $x
436 """
437
438## Other Command
439
440<h3 id="dparen" class="osh-topic">dparen ((</h3>
441
442<h3 id="time" class="osh-ysh-topic">time</h3>
443
444 time [-p] pipeline
445
446Measures the time taken by a command / pipeline. It uses the `getrusage()`
447function from `libc`.
448
449Note that time is a KEYWORD, not a builtin!
450
451<!-- Note: bash respects TIMEFORMAT -->
452
453
454## YSH Simple
455
456### typed-arg
457
458Internal commands (procs and builtins) accept typed arguments in parentheses:
459
460 json write (myobj)
461
462Redirects can also appear after the typed args:
463
464 json write (myobj) >out.txt
465
466### lazy-expr-arg
467
468Expressions in brackets like this:
469
470 assert [42 === x]
471
472Are syntactic sugar for:
473
474 assert (^[42 === x])
475
476That is, it's single arg of type `value.Expr`.
477
478Redirects can also appear after the lazy typed args:
479
480 assert [42 === x] >out.txt
481
482### block-arg
483
484Blocks can be passed to simple commands, either literally:
485
486 cd /tmp {
487 echo $PWD # prints /tmp
488 }
489 echo $PWD
490
491Or as an expression:
492
493 var block = ^(echo $PWD)
494 cd /tmp (; ; block)
495
496Note that `cd` has no typed or named arguments, so the two semicolons are
497preceded by nothing.
498
499Compare with [sh-block](#sh-block).
500
501Redirects can appear after the block arg:
502
503 cd /tmp {
504 echo $PWD # prints /tmp
505 } >out.txt
506
507## YSH Cond
508
509### ysh-case
510
511Like the shell case statement, the Ysh case statement has **string/glob** patterns.
512
513 var s = 'README.md'
514 case (s) {
515 *.py { echo 'Python' }
516 *.cc | *.h { echo 'C++' }
517 * { echo 'Other' }
518 }
519 # => Other
520
521We also generated it to **typed data** within `()`:
522
523 var x = 43
524 case (x) {
525 (30 + 12) { echo 'the integer 42' }
526 (else) { echo 'neither' }
527 }
528 # => neither
529
530The `else` is a special keyword that matches any value.
531
532 case (s) {
533 / dot* '.md' / { echo 'Markdown' }
534 (else) { echo 'neither' }
535 }
536 # => Markdown
537
538### ysh-if
539
540Like shell, you can use a command:
541
542 if test --file $x {
543 echo "$x is a file"
544 }
545
546You can also use an expression:
547
548 if (x > 0) {
549 echo 'positive'
550 }
551
552## YSH Iter
553
554### ysh-for
555
556#### Words
557
558This is a shell-style loop over "words":
559
560 for name in README.md *.py {
561 echo $name
562 }
563 # => README.md
564 # => foo.py
565
566You can also ask for the index:
567
568 for i, name in README.md *.py {
569 echo "$i $name"
570 }
571 # => 0 README.md
572 # => 1 foo.py
573
574#### Lines of `stdin`
575
576Here's how to iterate over the lines of stdin:
577
578 for line in (io.stdin) {
579 echo $line
580 }
581
582Likewise, you can ask for the index with `for i, line in (io.stdin) { ...`.
583
584### ysh-while
585
586You can use an expression as the condition:
587
588 var x = 5
589 while (x < 0) {
590 setvar x -= 1
591 }
592
593You or a command:
594
595 while test -f myfile {
596 echo 'myfile'
597 sleep 1
598 }
599
600#### Expressions
601
602Expressions are enclosed in `()`.
603
604Iterating over a `List` or `Range` is like iterating over words or lines:
605
606 var mylist = [42, 43]
607 for item in (mylist) {
608 echo $item
609 }
610 # => 42
611 # => 43
612
613 var n = 5
614 for i in (3 .. n) {
615 echo $i
616 }
617 # => 3
618 # => 4
619
620However, there are **three** ways of iterating over a `Dict`:
621
622 for key in (mydict) {
623 echo $key
624 }
625
626 for key, value in (mydict) {
627 echo "$key $value"
628 }
629
630 for i, key, value in (mydict) {
631 echo "$i $key $value"
632 }
633
634That is, if you ask for two things, you'll get the key and value. If you ask
635for three, you'll also get the index.
636