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

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