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