"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