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

645 lines, 409 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### block-arg
492
493Blocks can be passed to simple commands, either literally:
494
495 cd /tmp {
496 echo $PWD # prints /tmp
497 }
498 echo $PWD
499
500Or as an expression:
501
502 var block = ^(echo $PWD)
503 cd /tmp (; ; block)
504
505Note that `cd` has no typed or named arguments, so the two semicolons are
506preceded by nothing.
507
508Compare with [sh-block](#sh-block).
509
510Redirects can appear after the block arg:
511
512 cd /tmp {
513 echo $PWD # prints /tmp
514 } >out.txt
515
516## YSH Cond
517
518### ysh-case
519
520Like the shell case statement, the Ysh case statement has **string/glob** patterns.
521
522 var s = 'README.md'
523 case (s) {
524 *.py { echo 'Python' }
525 *.cc | *.h { echo 'C++' }
526 * { echo 'Other' }
527 }
528 # => Other
529
530We also generated it to **typed data** within `()`:
531
532 var x = 43
533 case (x) {
534 (30 + 12) { echo 'the integer 42' }
535 (else) { echo 'neither' }
536 }
537 # => neither
538
539The `else` is a special keyword that matches any value.
540
541 case (s) {
542 / dot* '.md' / { echo 'Markdown' }
543 (else) { echo 'neither' }
544 }
545 # => Markdown
546
547### ysh-if
548
549Like shell, you can use a command:
550
551 if test --file $x {
552 echo "$x is a file"
553 }
554
555You can also use an expression:
556
557 if (x > 0) {
558 echo 'positive'
559 }
560
561## YSH Iter
562
563### ysh-for
564
565#### Words
566
567This is a shell-style loop over "words":
568
569 for name in README.md *.py {
570 echo $name
571 }
572 # => README.md
573 # => foo.py
574
575You can also ask for the index:
576
577 for i, name in README.md *.py {
578 echo "$i $name"
579 }
580 # => 0 README.md
581 # => 1 foo.py
582
583#### Lines of `stdin`
584
585Here's how to iterate over the lines of stdin:
586
587 for line in (io.stdin) {
588 echo $line
589 }
590
591Likewise, you can ask for the index with `for i, line in (io.stdin) { ...`.
592
593### ysh-while
594
595You can use an expression as the condition:
596
597 var x = 5
598 while (x < 0) {
599 setvar x -= 1
600 }
601
602You or a command:
603
604 while test -f myfile {
605 echo 'myfile'
606 sleep 1
607 }
608
609#### Expressions
610
611Expressions are enclosed in `()`.
612
613Iterating over a `List` or `Range` is like iterating over words or lines:
614
615 var mylist = [42, 43]
616 for item in (mylist) {
617 echo $item
618 }
619 # => 42
620 # => 43
621
622 var n = 5
623 for i in (3 .. n) {
624 echo $i
625 }
626 # => 3
627 # => 4
628
629However, there are **three** ways of iterating over a `Dict`:
630
631 for key in (mydict) {
632 echo $key
633 }
634
635 for key, value in (mydict) {
636 echo "$key $value"
637 }
638
639 for i, key, value in (mydict) {
640 echo "$i $key $value"
641 }
642
643That is, if you ask for two things, you'll get the key and value. If you ask
644for three, you'll also get the index.
645