{"id":13778646,"url":"https://github.com/rain-1/s","last_synced_at":"2025-12-27T04:53:25.029Z","repository":{"id":92385455,"uuid":"292180308","full_name":"rain-1/s","owner":"rain-1","description":"reupload of s, used to have 100 stars","archived":false,"fork":false,"pushed_at":"2021-01-08T14:18:09.000Z","size":141,"stargazers_count":68,"open_issues_count":0,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-08-03T18:13:10.296Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rain-1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"supporting/glob.c","governance":null,"roadmap":null,"authors":null}},"created_at":"2020-09-02T04:39:48.000Z","updated_at":"2024-07-31T19:14:36.000Z","dependencies_parsed_at":"2023-05-17T02:00:14.227Z","dependency_job_id":null,"html_url":"https://github.com/rain-1/s","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rain-1%2Fs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rain-1%2Fs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rain-1%2Fs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rain-1%2Fs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rain-1","download_url":"https://codeload.github.com/rain-1/s/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225048972,"owners_count":17412902,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T18:00:55.658Z","updated_at":"2025-12-27T04:53:24.986Z","avatar_url":"https://github.com/rain-1.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"# s\n\nAfter the [Shellshock](https://en.wikipedia.org/wiki/Shellshock_(software_bug))\nbug happened, they fixed the parsing bug in bash that let people hijack\nwebservers by simply including `{ :; }`.\n\nFixing a bug after it's been found is good but it doesn't have to be the end.\nOften you should think about the bigger picture and see why it was ever a bug\nin the first place.\n\nI suspected that since bash syntax is very complex, the parser is going to be a\nlot of complicated code too. So I did some line counts of various shells to see\nwhat the situation is like:\n\n                  .c       .h\n    gnu bash:     138227,  13746\n    zsh:          135589,   5698\n    shivers scsh: 118475 (scheme)\n    templeos:     119115\n    mirbsd mksh:   29223,   2562\n    debian dash:   16503,   2084\n    freebsd sh:    15453,   1622\n    es shell:       9017,   1436\n    plan9 rc:       5989,    327\n    execline:       3794,    117\n\nThe line count for TempleOS isn't just counting its shell. It's the linecount\nof the entire system: The operating system kernel, the compiler, the shell, the\ndrawing program, the graphical user interface, the 3D flight simulator. **Bash,\nzsh and scsh have more code in them than an entire operating system.**\n\nShell syntax being overcomplex and ideosyncratic is a long-standing problem.\nOther than shellshock, annoyances happen on a daily basis. Like the time a bug\nin a bash script in steam rm -rf'd people's home directory [Ran steam. It deleted everything](https://github.com/valvesoftware/steam-for-linux/issues/3671).\nI've had makefiles break a few times because there's a space in my path. It\ntook these people four years to fix that [Build failure when builddir contains\nspaces](https://github.com/nodejs/node-gyp/issues/65).\n\nMy idea to improve things is to use an extremely minimal shell with the\nsimplest syntax possible.\n\n# The new command interpreter\n\nSo I designed a new shell that aims to be extremely simple, and implemented it\nin a small number of lines of code. Right now it's 780 lines plus 1200 for the\nlinenoise library. (Technically it is a \"command interpreter\" not a shell since\nshell implies it implements the POSIX shell standard).\n\nHere is the man page for *s*:\n\n```\nS(1)                      BSD General Commands Manual                      S(1)\n\nNAME\n     s — command line interpreter\n\nSYNOPSIS\n     s [-dvh] [SCRIPT ...]\n\nDESCRIPTION\n     s has two main modes of operation: interactive shell and script processor.\n\n     If the SCRIPT is provided it will execute that file with optional given\n     arguments.  Alternatively you can provide commands via stdin.\n\n     Otherwise if you start s in a terminal you will be given an interactive\n     shell prompt like this:\n\n             s$ echo hello world!\n             hello world!\n\nOPTIONS\n     -d      Enable debug mode, reporting becomes more verbose showing file and\n             line number of where the error or warning occurred.\n\n     -v      Print version and copyright info and exit.\n\n     -h      Print usage info and exit.\n\nSYNTAX\n     The lexical syntax of the shell language is very strictly tokenized based\n     on spaces.\n\n     Variables may occur inside tokens with ${FOO} syntax, the {}'s are\n     optional for when variables need to be next to other strings:\n\n             s$ set FOO bar\n             s$ set avar value\n             s$ echo $FOO ${avar}\n             bar value\n             s$ echo $FOO$avar\n             barvalue\n             s$ echo ${FOO}string$avar\n             barstringvalue\n\n     See the BUILTINS section below for info on setting variables.\n\n     If found at the beginning of a token the ~ character expands to ${HOME} as\n     a shortcut for the user's home directory.\n\n     Single and double quotes are used to group the enclosed strings into one\n     token.  Variables are only expanded inside double quotes. You can escape\n     characters in quotes by using backslashes, allowing you to insert the same\n     quote character inside a string, as well as inserting newline, tab, and\n     spaces. Quotes can also be inserted inside the other quote types without\n     escaping them.\n\n             s$ echo \"I am using the '$SHELL' shell\"\n             I am using the '/bin/s' shell\n             s$ echo 'I am using the \"$SHELL\" shell'\n             I am using the \"$SHELL\" shell\n\n             s$ echo \"you can use backslash as well: \\\" \\'\"\n             you can use backslash as well: \" '\n\n             $ echo 'person\\tbalance\\nRichard\\t$500\\nJohn\\t$300'\n             person  balance\n             Richard $500\n             John    $300\n\n     Backticks can also be used to expand commands quoted inside to their out‐\n     put:\n\n             s$ xargs rm `cat oldfiles.txt` # delete all files found in oldfiles.txt\n             s$ set CFILES `glob *.c`   # make $CFILES equal to a list of all C files\n\n     The # character, as seen above, starts a comment until the end of the\n     line.\n\n     The grammar is line based. Sequences of tokens are treated as commands and\n     the operators |, \u0026\u0026, and || are parsed in order of tightest binding first.\n\n     File redirection is done with separate commands,\n     pipes are needed:\n\n             s$ echo \"hello world\" | \u003e file.txt\n             s$ echo \"hello world again\" | \u003e\u003e file.txt\n             s$ cat | \u003c file.txt\n             hello world\n             hello world again\n\n     \u0026 at the end of a line can specify that a job should be run in the back‐\n     ground.\n\n     A backslash at the end of the line allows for commands to span multiple\n     lines.\n\nBUILTINS\n     source SCRIPT [...]\n             Open and execute commands in SCRIPT with the optional arguments\n             provided.\n\n     cd [DIR | -]\n             Change current working directory to DIR.  If DIR is not supplied,\n             change to $HOME instead.  If - is given switch directories to pre‐\n             vious working directory, stored in $OWD.\n\n     set VAR VAL\n             Set the environment variable given by VAR equal to VAL.\n\n     unset VAR\n             Remove environment variable VAR.\n\n     exit [N]\n             Exit s with code N, defaults to 0.\n\nENVIRONMENT\n     SHELL   The full pathname to the shell.\n\n     HOME    The home directory of the current user.\n\n     PWD     Current working directory.\n\n     OWD     Previous working directory.\n\n     PATH    A colon-separated list of directories in which s looks for com‐\n             mands in.\n\nAUTHORS\n     rain-1 \u003crain1@openmailbox.org\u003e\n\n     Ed van Bruggen \u003cedvb54@gmail.com\u003e\n\nLICENSE\n     BSD 3 Clause\n\nSEE ALSO\n     execline(1)\n\n     View source code and report bugs at: \u003chttps://github.com/rain-1/s\u003e\n\nS v0.0.0                          Jun 28, 2017                         S v0.0.0\n```\n\nThe important thing is what's missing: we don't have globs or \"splatting\" where\na variable `$FOO` turns into multiple command line arguments. One token stays\none token forever. This is a \"no surprises\" straightforward approach.\n\nLook at how many ways one can screw up passing or copy an array in this [oil shell blog post](http://www.oilshell.org/blog/2016/11/06.html)\n\nWe don't even have redirection operators `\u003e` in the shell language. They are\nadded as extra programs. So you write `grep foo bar | \u003e out.txt`. `\u003e` is just\nanother Unix command. `\u003c` is essentially `cat`. A `glob` program is also\nprovided along with *s*, so you write `glob rm *`.\n\nThe benefit of this extreme minimalism is that the implementation can be done\nwith a very logical pipeline of operations: tokenizing splits the input based\non spaces and expands the variables out, parsing looks for the operators and\nbuilds an AST, interpreting is a simple recursive walk down the AST\nimplementing the various operators.\n\n* [tokenizer.c](https://github.com/rain-1/s/blob/master/tokenizer.c)\n* [variables.c](https://github.com/rain-1/s/blob/master/variables.c)\n* [parser.c](https://github.com/rain-1/s/blob/master/parser.c)\n* [interpreter.c](https://github.com/rain-1/s/blob/master/interpreter.c)\n* [repo](https://github.com/rain-1/s)\n\n# Trying it out\n\n* `git clone --recurse-submodules git@github.com:rain-1/s.git`\n* `./makesfile`\n* `./s`\n\n# Changes\n\nWhat's new with s:\n\n* **s supports russian, japanese, unicode** We have now switched to the yhirose utf8-support branch of [linenoise](https://github.com/yhirose/linenoise/tree/utf8-support).\n* **s does not use makefiles!** No more nasty makefiles. ./make builds it using a super simple shell script.\n\n# End\n\nWhat do you think? I would be very interested in hearing people's opinions\nabout *s* and the more general idea of stripping programs down to their\nessential parts. Feel free to comment or make 'issues' on the repo if you have\nany criticism, suggestions or just want to discuss.\n\n# See also\n\n* [oh](https://github.com/michaelmacinnis/oh) + https://www.youtube.com/watch?v=v1m-WEZz46U\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frain-1%2Fs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frain-1%2Fs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frain-1%2Fs/lists"}