~/sh(1) en

SH(1)General Commands ManualSH(1)

NAME

sh - the shell itself

SYNOPSIS

sh [file]

DESCRIPTION

The shell on the front page. It is not bash, but what you know about bash holds here: the flags mean what they mean, the errors are the errors you would get, and a command that cannot do a thing says which thing rather than shrugging.

Composing

| sends one command’s output to the next, > writes it to a file and >> adds to one. ; runs one line after another. They need no space around them, so ls>/tmp/out.txt is the same as ls > /tmp/out.txt.

A redirection belongs to the command it follows, so in ls > f | head the listing goes to f and head is handed nothing — as it would be in bash. A redirection with no command at all truncates the file.

Output knows where it is going. ls at the prompt prints columns and through a pipe prints one name per line; grep marks its matches at the prompt and not through a pipe. The real tools do the same thing when their output is not a terminal. Errors ignore all of it and come to you, because they are stderr.

Words and what they turn into

Between reading a line and running it there is one pass, and it is bash’s, in bash’s order.

$NAME and ${NAME} are a variable’s value; ${NAME:-something} is that value or, when there is none, the something. $? is what the last command answered, $0 the name of the script running, $1 its first argument, $# how many there are. NAME=value on its own makes one; in front of a command it belongs to that command alone. man export has the rest, and set prints the lot.

$(...) runs a command and hands back what it printed, with the newline at the end taken off: echo $(pwd), and for i in $(seq 3).

A leading ~ is your home directory. *, ? and [abc] match the names in a directory – cat *.md, ls ~/writing/*vegan* – and a pattern that matches nothing stays the pattern, which is bash’s default and the reason echo *.zip prints *.zip.

Then the words are split on whitespace, which is where quoting starts to matter: $A is as many words as its value has spaces, and "$A" is one.

Quoting

"..." and '...' hold a name with spaces together, which is how you reach a note like cat "Site test 2.md", and how you keep an operator as an argument: grep "a | b" searches for a pipe. Tab quotes what it inserts when it needs to.

The difference between the two is what happens inside. Double quotes still expand a variable and still run a $(...); single quotes do nothing at all, which is why PS1='\w\$ ' is written with them. A backslash is the same promise about one character: echo \$HOME prints a dollar, and cat Site\ test\ 2.md is that note without a quote in sight.

A # where a word could start is a comment to the end of the line.

Deciding

Every command answers with a number: 0 for “that worked”. echo $? reads the last one, and everything below is reading the same number.

&& runs the next command only if this one worked, || only if it did not, and ! turns the answer over. if, elif, else and fi take whole lists – the answer is the last command in the condition:

if grep -q vegan about.md; then echo found; else echo nothing; fi

test, written [ ... ], is what a condition usually is: [ -f about.md ], [ -d /tmp ], [ "$USER" = tilen ]. It reads the same mode bits ls -l shows, so it knows what you may read and write. man test has the list.

true and false are commands that do nothing but answer, which is what they are for.

Repeating

for name in words; do ...; done walks a list, which is what globbing and $(...) are usually making:

for f in *.md; do echo "$f: $(wc -l $f)"; done

while and until take a condition rather than a list. There is no Ctrl-C here that can stop a loop, so one line may run five thousand commands and is then stopped with a word about it. That is the only rule in this shell that exists because it lives in a browser tab.

Scripts

A file of commands is a script, and running one is the ordinary three steps:

echo 'echo hello from $0' > /tmp/hi.sh
chmod +x /tmp/hi.sh
/tmp/hi.sh

$0 is the file, $1 upwards are what you gave it, $@ all of them. A #! line is a comment, as it is to any shell reading a script it was handed. sh file runs one without the x bit, because you are the one running it; source file runs it here, so what it sets stays set – see man source.

~/.zshrc is read at every login, which is where PS1, PATH and the aliases come from. It is an ordinary file: read it, and change it if it is yours.

Where you are

You are guest, and ~ is /home/tilen: this session’s home directory is the site’s own, which is why it is full of somebody else’s files. /etc/passwd says as much, in the words the system would use. The machine is around it — ls /, and man hier for what is where.

Who may write what

The ordinary rule, read off the mode bits ls -l shows: the owner gets the first three, everybody else the last three. Everything the site published was rendered by Hugo into tilen’s home directory and is -rw-r--r-- tilen, so as a guest you may read all of it and write none of it — Permission denied, which is the truth rather than a stand-in for one. Some of what is in ~ is -rw-------, and a guest may not read that either.

su tilen is how that changes, and man su is the page about it. /tmp belongs to nobody and is anybody’s: drwxrwxrwt, sticky, the way /tmp always has been.

Where what you write goes

Two places, and they behave differently on purpose.

/tmp is the machine. What you leave there stays until you remove it.

~ is the build output. A line appended to a note, a note replaced, renamed, removed or chmod‘ed lies over what Hugo rendered and lasts as long as this build does. The real build renders the vault into a brand new directory and swaps a symlink, so nothing anybody left in the old one is in the new one — which is why a reload puts every note back, and why tilenkoren-build does it on demand. Opening a note and coming back is not a reload, so your change survives the trip and shows up on the page itself, marked as yours.

Either way it is in this browser and nowhere else — localStorage and sessionStorage, 64KB each, then No space left on device. There is no backend to send it to, so nothing you write is published: a file you made lists in grey — though it has a page of its own to open, /file/, which reads it back out of this browser — and a note you changed keeps its own page and changes colour.

cp, mv, rm, touch and chmod say why when they will not do a thing. cp about.md /tmp/mine.md is the way to get a copy you can change without being anybody in particular, which is what you would do on any read-only filesystem.

At the prompt

Tab completes, Ctrl-C abandons the line, Ctrl-L and clear empty the screen, the up arrow walks history, and q in a reading view comes back here. After a - it offers the command’s flags, each with the line its manual page gives it. See man help for what Tab offers where.

A line that cannot end yet – an if with no fi, an open quote, a trailing pipe – is not an error: the prompt becomes $PS2 and waits for the rest of it. Ctrl-C abandons the whole thing.

PS1 is what the prompt is made of, in the escapes bash uses: \u you, \h the host, \w where you are, \W its last part, \$ the sigil, \n a new line, \t the time, and \e[32m for colour. The default is \u@\h:\w\$ , which is the prompt you started with. The pieces come coloured by what they are – a user, a path, a sigil – and an escape overrides that where you write one; a real terminal has no idea what any of them mean, and colours only what it is told to.

history prints what the up arrow walks, !! is the last line and !$ its last word.

One thing to know about text: a note’s “lines” are its paragraphs, since that is the shape the shell is handed, so head -2 and grep -n count paragraphs rather than the lines of the file in the vault.

EXAMPLES

ls | head -3

NAME=world; echo "hello $NAME"

if grep -q vegan about.md; then echo found; fi

for f in *.md; do wc -l $f; done

sh /tmp/notes.sh

SEE ALSO

help(1), hier(7), test(1), export(1), alias(1), source(1)

tilenkoren.com2026SH(1)