"Bash isn't Webscale"
A single-page web chat application, backed by a Bash script
Like cat(1) for TCP/UDP sockets.
$ printf "HTTP/1.1 200 OK\r\n\r\nHello world\n" |
nc -l 8080
$ curl localhost:8080 Hello world
$ printf "HTTP/1.1 200 OK\r\n\r\nHello world\n" |
nc -l 8080
GET / HTTP/1.1
User-Agent: curl/7.32.0
Host: localhost:8080
Accept: */*
No callbacks.
Branching on input.
coproc NAME COMMAND
$ coproc rot13 { stdbuf -oL tr '[A-Za-z]' '[N-ZA-Mn-za-m]'; }
[1] 26812
$ echo ${rot13[0]}
63
$ echo ${rot13[1]}
60
$ echo "hello, world" >&${rot13[1]}
$ echo "this is a secret" >&${rot13[1]}
$ read line <&${rot13[0]}
$ echo $line
uryyb, jbeyq
$ read line <&${rot13[0]}
$ echo $line
guvf vf n frperg
$ kill ${rot13_PID}
Closing a filehandle
somecommand 1&>-
Closing a filehandle without a command
exec 60&>-
Interpolating the coprocess STDIN
exec ${rot13[1]}&>-
(Doesn't parse correctly)
Using eval
eval "exec ${rot13[1]}&>-"
Simple, right?
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn!
# Start netcat
coproc nc ( nc -l 8080 )
# Plug it into a request handler
handle_request ${nc_PID} <&${nc[0]} >&${nc[1]}
# Close filehandles
eval "exec ${nc[1]}>&-"
eval "exec ${nc[0]}<&-"
rot13() {
echo "$1" | stdbuf -oL tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
}
$ rot13 "pssst" cfffg
rot13() {
stdbuf -oL tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
}
$ echo "pssst" | rot13 cfffg
stdbuf -oL tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
stdbuf -oL tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
foo() {
bar=42 # global!
}
$ foo
$ echo ${bar}
42
foo() {
local bar=42 # local
}
$ foo
$ echo ${bar} # no output
HTTP/1.1 GET /index.html
text/html, application/json, etc.
(AKA Map, Dictionary, Hash)
declare -A content_types=([js]=text/javascript
[html]=text/html)
$ echo ${content_types[html]}
text/html
With default:
$ echo ${content_types[foo]-text/plain}
text/plain
$ file=public/index.html
$ echo ${file##*.}
html
local file="public/${path}"
if [ -f "${file}" ]; do
local ext="${file##*.}"
local type="${content_types[${ext}]}"
printf "HTTP/1.1 200 OK\r\n"
printf "Content-Type: ${type}\r\n\r\n"
cat "${file}"
fi
But this is Bash...
B-List Actor
agar() {
# ...
}
local name=$1
local args=${@:1}
mkdir -p fifos
local fifoname=fifos/${name}
mkfifo ${fifoname}
{
queue=${fifoname} ${name} ${args[@]}
rm -f ${fifoname}
} &
send() {
local dest=$1
local message=${*:1}
echo "${message}" > fifos/${dest} &
}
agar main
main() {
while true; do
serve_with_coproc &
read < ${queue}
done
}
# ... read req_line send main "continue" # ...
{
"name": "Avdi",
"text": "Testing 1 2 3"
}
Everyone's favorite NoSQL document database...
CREATE TABLE messages (
id serial PRIMARY KEY,
content json,
posted_at timestamptz DEFAULT now()
);
INSERT INTO messages (content) VALUES
('{
"name": "Bullwinkle",
"text": "Hey Rocky, watch me pull a rabbit out of my hat"
}');
INSERT INTO messages (content) VALUES
('{ "name": "Rocky", "text": "Again?" }');
INSERT INTO messages (content) VALUES
('{ "name": "Bullwinkle", "text": "Presto!" }');
SELECT content->>'text' AS text FROM messages WHERE content->>'name' = 'Bullwinkle';
text ------------------------------------------------- Hey Rocky, watch me pull a rabbit out of my hat Presto! (2 rows)
No need to parse JSON in Bash!
"Bash isn't Webscale"
I got it working... barely.
This was a terrible idea!
"The one dependency we can count on"
"It just continuously cracks me up."
—Rick Hohensee
"What a pointless waste of time"
—My brain
"If you ask questions that are really dumb, eventually you know things"
— Julia Evans
"How do I make the next Instagram?"
"How do I write a kernel?"
Comes from asking outlier questions
When we experiment—when we try things, and we fail—we start to ask why, and that’s when we learn.
–Jamie Hyneman
A story to tell
Abstractions stripped away
Greater familiarity with tools
Increased comfort with Bash scripting
It was fun
Code: https://github.com/avdi/walrus
I make screencasts: RubyTapas.com
@avdi / avdi@avdi.org