├── .github
└── FUNDING.yml
├── .gitignore
├── .golangci.yml
├── AUTHORS
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bin
├── build.oh
├── check.oh
├── doc.oh
├── doctest
│ ├── 000-continuations-test.oh
│ ├── 000-dynamic-test.oh
│ ├── 000-exceptions-test.oh
│ ├── 000-homoiconic-test.oh
│ ├── 000-local-test.oh
│ ├── 000-similar-in-spirit-test.oh
│ ├── 010-intro-manual.oh
│ ├── 100-commands-manual.oh
│ ├── 120-simple-manual.oh
│ ├── 130-redirection-manual.oh
│ ├── 140-pipelines-manual.oh
│ ├── 150-globs-manual.oh
│ ├── 160-quoting-manual.oh
│ ├── 200-programming-manual.oh
│ ├── 240-control-manual.oh
│ ├── 243-control-while-manual.oh
│ ├── 252-objects-object-manual.oh
│ ├── 253-objects-method-manual.oh
│ ├── 254-objects-point-manual.oh
│ ├── 255-objects-patterns-manual.oh
│ ├── 256-objects-syntax-manual.oh
│ ├── 260-maps-manual.oh
│ └── 280-channels-manual.oh
├── test.oh
└── type-common.oh
├── doc
├── comparison.html
└── manual.md
├── go.mod
├── go.sum
├── internal
├── common
│ ├── common.go
│ ├── interface
│ │ ├── boolean
│ │ │ └── boolean.go
│ │ ├── cell
│ │ │ └── cell.go
│ │ ├── conduit
│ │ │ └── conduit.go
│ │ ├── integer
│ │ │ └── integer.go
│ │ ├── literal
│ │ │ └── literal.go
│ │ ├── rational
│ │ │ └── rational.go
│ │ ├── reference
│ │ │ └── reference.go
│ │ └── scope
│ │ │ └── scope.go
│ ├── struct
│ │ ├── frame
│ │ │ └── frame.go
│ │ ├── hash
│ │ │ └── hash.go
│ │ ├── loc
│ │ │ └── loc.go
│ │ ├── slot
│ │ │ └── slot.go
│ │ └── token
│ │ │ └── token.go
│ ├── type
│ │ ├── chn
│ │ │ ├── chn.go
│ │ │ ├── chn_internal_test.go
│ │ │ └── generated.go
│ │ ├── create
│ │ │ └── create.go
│ │ ├── env
│ │ │ ├── env.go
│ │ │ └── generated.go
│ │ ├── list
│ │ │ └── list.go
│ │ ├── num
│ │ │ ├── generated.go
│ │ │ └── num.go
│ │ ├── obj
│ │ │ ├── generated.go
│ │ │ └── obj.go
│ │ ├── pair
│ │ │ ├── generated.go
│ │ │ └── pair.go
│ │ ├── pipe
│ │ │ ├── generated.go
│ │ │ ├── pipe.go
│ │ │ └── pipe_internal_test.go
│ │ ├── status
│ │ │ ├── generated.go
│ │ │ └── status.go
│ │ ├── str
│ │ │ ├── generated.go
│ │ │ └── str.go
│ │ └── sym
│ │ │ ├── conversion.go
│ │ │ ├── plus.go
│ │ │ └── sym.go
│ └── validate
│ │ └── validate.go
├── engine
│ ├── boot
│ │ ├── boot.go
│ │ └── boot.oh
│ ├── commands
│ │ ├── arithmetic.go
│ │ ├── channel.go
│ │ ├── commands.go
│ │ ├── conduit.go
│ │ ├── core.go
│ │ ├── file.go
│ │ ├── list.go
│ │ ├── logical.go
│ │ ├── number.go
│ │ ├── pair.go
│ │ ├── pipe.go
│ │ ├── relational.go
│ │ ├── string.go
│ │ ├── symbol.go
│ │ └── umask_unix.go
│ ├── engine.go
│ └── task
│ │ ├── action.go
│ │ ├── closure.go
│ │ ├── method.go
│ │ ├── operation.go
│ │ ├── registers.go
│ │ ├── state.go
│ │ ├── syntax.go
│ │ └── task.go
├── reader
│ ├── lexer
│ │ ├── lexer.go
│ │ └── lexer_internal_test.go
│ ├── parser
│ │ ├── parser.go
│ │ └── parser_internal_test.go
│ └── reader.go
└── system
│ ├── cache
│ └── cache.go
│ ├── history
│ ├── history.go
│ └── os_unix.go
│ ├── job
│ └── job_unix.go
│ ├── options
│ └── options.go
│ └── process
│ └── process_unix.go
└── main.go
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: michaelmacinnis
2 | patreon: "user?u=20026503"
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | local.*
3 | oh*
4 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | enable-all: true
3 | disable:
4 | - exhaustivestruct
5 | - paralleltest
6 | - scopelint
7 | - wrapcheck
8 |
9 | linters-settings:
10 | exhaustive:
11 | default-signifies-exhaustive: true
12 | funlen:
13 | lines: 96
14 | statements: 64
15 |
16 | issues:
17 | exclude-rules:
18 | - path: _test\.go
19 | linters:
20 | - errcheck
21 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | michaelmacinnis
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | If you would like to contribute to oh, please discuss the change first via the
2 | issue tracker.
3 |
4 | When contributing, you must agree to license your code under the same license
5 | as the existing source sode for oh. See [LICENSE](LICENSE) for details.
6 |
7 | On your first pull request, add your GitHub username to [AUTHORS](AUTHORS) to
8 | affirm that the code you are contributing is your own and that you agree to
9 | these terms.
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2011-2022 Michael MacInnis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Oh, a new Unix shell
2 |
3 | ## Why oh?
4 |
5 | Oh is a reimagining of the Unix shell.
6 |
7 | Oh provides:
8 |
9 | - A simplified set of evaluation and quoting rules;
10 | - Rich return values that work with standard shell constructs;
11 | - First-class channels, pipes, environments and functions;
12 | - A list type (no word splitting);
13 | - Support for modularity;
14 | - Lexical scope;
15 | - Exceptions;
16 | - Kernel-style fexprs (allowing the definition of new language constructs); and
17 | - A syntax that deviates as little as possible from established conventions;
18 |
19 | Oh was motivated by the belief that many of the flaws in current Unix shells
20 | are not inherent but rather historical. Design choices that are now clearly
21 | unfortunate in retrospect have been carried forward in the name of backward
22 | compatibility.
23 |
24 | Oh's goal is a language that is not only more powerful and more regular but
25 | one that respects the conventions established by the Unix shell over the last
26 | half-century.
27 |
28 | ## Getting started
29 |
30 | ### Installing
31 |
32 | The easiest way to try oh is to download a precompiled binary.
33 |
34 |
35 | #### DragonFly BSD
36 |
37 | [amd64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-dragonfly-amd64)
38 |
39 | #### FreeBSD
40 |
41 | [386](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-freebsd-386), [amd64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-freebsd-amd64), [arm](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-freebsd-arm), [arm64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-freebsd-arm64), [riscv64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-freebsd-riscv64)
42 |
43 | #### illumos
44 |
45 | [amd64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-illumos-amd64)
46 |
47 | #### Linux
48 |
49 | [386](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-386), [amd64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-amd64), [arm](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-arm), [arm64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-arm64), [mips](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-mips), [mips64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-mips64), [mips64le](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-mips64le), [mipsle](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-mipsle), [ppc64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-ppc64), [ppc64le](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-ppc64le), [riscv64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-riscv64), [s390x](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-linux-s390x)
50 |
51 | #### macOS
52 |
53 | [amd64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-darwin-amd64), [arm64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-darwin-arm64)
54 |
55 | #### OpenBSD
56 |
57 | [386](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-openbsd-386), [amd64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-openbsd-amd64), [arm](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-openbsd-arm), [arm64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-openbsd-arm64)
58 |
59 | #### Solaris
60 |
61 | [amd64](https://github.com/michaelmacinnis/oh/releases/download/v0.8.3/oh-v0.8.3-solaris-amd64)
62 |
63 | You can also build oh from source. With Go 1.21 or later installed, type,
64 |
65 | go install github.com/michaelmacinnis/oh@v0.8.3
66 |
67 | ### Configuring
68 |
69 | When oh starts, it attempts to read a file called `.oh-rc` in the home
70 | directory of the current user. You can override this path by setting
71 | the OH_RC environment variable to the full path of an alternative file
72 | before invoking oh.
73 |
74 | The oh rc file is useful for setting environment variables and defining
75 | custom commands. It's also a good place to override oh's default prompt.
76 | The command below replaces oh's default prompt method with one that
77 | displays the current date.
78 |
79 | replace-make-prompt (method (suffix) {
80 | return `(date)$suffix
81 | })
82 |
83 | Oh (thanks to peterh/liner) also provides a searchable command history.
84 | By default, this history is stored in a file called `.oh-history` in
85 | your home directory. You can override this by setting the OH_HISTORY
86 | environment variable to the full path of an alternative file before
87 | invoking oh.
88 |
89 | ## Comparing oh to other Unix shells
90 |
91 | Oh is a Unix shell. If you've used other Unix shells, oh should feel
92 | familiar. Below are some specific differences you may encounter.
93 |
94 | ### Clobbering
95 |
96 | When redirecting output oh will not overwrite an existing file. To force
97 | oh to overwrite (clobber) an existing file add a pipe, `|`, character
98 | immediately after the redirection operator. For example,
99 |
100 | command >| out.txt
101 |
102 | Oh's pipe and redirection syntax is as follows.
103 |
104 | | Syntax | Redirection |
105 | |----------:|:----------------------------------:|
106 | | `<` | input-from |
107 | | `>` | output-to |
108 | | `>&` | output-errors-to |
109 | | `>&\|` | output-errors-clobbers |
110 | | `>>` | append-output-to |
111 | | `>>&` | append-output-errors-to |
112 | | `>\|` | output-clobbers |
113 | | `\|` | pipe-output-to |
114 | | `\|&` | pipe-output-errors-to |
115 | | `\|<` | -named-pipe-input-from* |
116 | | `\|>` | -named-pipe-output-to* |
117 |
118 | \* - Used in process substitution.
119 |
120 | ### Command substitution
121 |
122 | Many Unix shells support command substitution using the historical
123 | backtick syntax,
124 |
125 | `command`
126 |
127 | or the POSIX syntax,
128 |
129 | $(command)
130 |
131 | Oh has one syntax for command substitution,
132 |
133 | `(command)
134 |
135 | This syntax is both nestable and unambiguous.
136 |
137 | ### Here documents
138 |
139 | Oh does not have here documents. It does however allow strings to span
140 | lines and provides a `here` command that takes a string argument and can
141 | be used to the same effect. For example,
142 |
143 | # Build oh for supported BSD platforms
144 | here "
145 | dragonfly amd64
146 | freebsd 386
147 | freebsd amd64
148 | freebsd arm
149 | freebsd arm64
150 | openbsd 386
151 | openbsd amd64
152 | openbsd arm
153 | openbsd arm64
154 | openbsd mips64
155 | " | mill (o a) {
156 | echo ${o}/${a}
157 | GOOS=${o} GOARCH=${a} go build -o oh-latest-${o}-${a}
158 | }
159 |
160 | ### Variables
161 |
162 | To introduce a new variable, use the `define` command,
163 |
164 | define x 3
165 |
166 | To introduce a variable that will be visible to external processes,
167 | use the `export` command,
168 |
169 | export GOROOT /usr/local/go
170 |
171 | To set the value of an existing variable, use the `set` command,
172 |
173 | set x 4
174 |
175 | ### Variables and implicit concatenation
176 |
177 | Like other shells, oh implicitly concatenates adjacent string/symbol
178 | values. Unlike other shells, oh allows a larger set of characters to
179 | appear in variable names. In addition to letters, numbers, and the
180 | underscore character, the following characters,
181 |
182 | '!', '%', '*', '+', '-', '?', '[', ']', and '^'
183 |
184 | can be used in oh variable names. The command,
185 |
186 | echo $set!
187 |
188 | will cause oh to attempt to resolve a variable called `set!`.
189 | The following characters,
190 |
191 | ',', '.', '/', ':', '=', '@', and '~'
192 |
193 | always result in a symbol of one character. This ensures that commands
194 | like,
195 |
196 | cd $PWD/$dir
197 |
198 | work as expected. When using implicit concatentation, unexpected behavior
199 | can be avoided by enclosing variable names in braces.
200 |
201 | ### More detailed comparison
202 |
203 | For a detailed comparison to other Unix shells see: [Comparing oh to other Unix Shells](https://htmlpreview.github.io/?https://raw.githubusercontent.com/michaelmacinnis/oh/master/doc/comparison.html)
204 |
205 | ## Using oh
206 |
207 | For more information on using oh, see: [Using oh](doc/manual.md)
208 |
209 | ## Contributing to oh
210 |
211 | Oh is an ongoing experiment and it needs your help. Try oh. Let me know
212 | what works for you and what doesn't.
213 |
214 | Pull requests are welcome. For information on contributing, see: [CONTRIBUTING](CONTRIBUTING.md)
215 |
216 | You can also sponsor me through GitHub Sponsors or Patreon.
217 |
218 | ## License
219 |
220 | [MIT](LICENSE)
221 |
222 |
--------------------------------------------------------------------------------
/bin/build.oh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env oh
2 |
3 | # To see missing GOOS/GOARCH pairs compare with: go tool dist list
4 |
5 | define t `(git describe --abbrev=0 --tags)
6 |
7 | here "
8 | aix ppc64 - cannot use unix.TIOCSPGRP (untyped int constant 18446744071562359926) as int value in argument to unix.IoctlSetPointerInt (overflows)
9 | darwin amd64
10 | darwin arm64
11 | dragonfly amd64
12 | freebsd 386
13 | freebsd amd64
14 | freebsd arm
15 | freebsd arm64
16 | freebsd riscv64
17 | illumos amd64
18 | linux 386
19 | linux amd64
20 | linux arm
21 | linux arm64
22 | linux mips
23 | linux mips64
24 | linux mips64le
25 | linux mipsle
26 | linux ppc64
27 | linux ppc64le
28 | linux riscv64
29 | linux s390x
30 | netbsd 386 - undefined: unix.WCONTINUED
31 | netbsd amd64 - undefined: unix.WCONTINUED
32 | netbsd arm - undefined: unix.WCONTINUED
33 | netbsd arm64 - undefined: unix.WCONTINUED
34 | openbsd 386
35 | openbsd amd64
36 | openbsd arm
37 | openbsd arm64
38 | solaris amd64
39 | " | grep -v ' - ' | sed -re 's/ - .*$//g' | mill (o a) {
40 | echo ${o}/${a}
41 | GOOS=${o} GOARCH=${a} go build -ldflags='-s -w' -o oh-${t}-${o}-${a} -trimpath
42 | }
43 |
--------------------------------------------------------------------------------
/bin/check.oh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env oh
2 |
3 | golangci-lint run --sort-results | grep -Fv TODO
4 |
5 | # To see the silenced linter warnings.
6 | # git grep nolint | grep -Ev 'checkno(globals|inits)' | grep -v implements
7 |
--------------------------------------------------------------------------------
/bin/doc.oh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env oh
2 |
3 | if (ne? 2 (@ length)) {
4 | error "usage: $0