I always use the pipe, it is intuitive to me to think left-to-right, output of this becomes input of next. But I always see stackoverflow answers using the other approach.
The difference is in the first code example, you're launching both cat and nc.
In the second block, you're just launching one program that reads input, not two. It's slightly more efficient.
I also tend to build large *NIX pipelines using `cat $foo | [...]`, but it's some times regarded as bad form. See: "useless use of cat" for more examples.
Unfortunately, gawk doesn't let you control the listen/accept part of it, so it doesn't scale well. Not sure why they did it that way. A separate accept() call would have made it actually usable in a non-toy way.
My humble submission to this genre is this pipeline I wrote once, when I needed an HTTP proxy, but I couldn't use install a real one do to work limitations, and I couldn't use firewall rules because of weird limitations of the Mac OS at the time:
Thanks! TIL! That's a much cleaner way of doing it.
Regarding keeping the file, if memory serves the "real" pipeline had some `tee`s in it. I recreated this much more recently to make a GitHub repo of funny code snippets (https://github.com/MaxBondABE/one-thread-crashing/).
You reminded me of a different toy I made many years ago. I created FIFOs for files that you might try to read when exploiting an arbitrary file read in a web app, say /etc/www/apache.conf (if that's the right path - it's been a while since I've configured a web server!) and I'd have a program that opened them. This would block until someone opened the other end of the FIFO, at which point I'd raise an alarm (which was just a print statement).
I'm not OP, but as they said, it's an HTTP proxy. Run it, then point your browser at http://localhost:8080. The message is proxied over to http://httpbin.org (on port 80, the normal HTTP port). If you're in an overly restricted environment and have a legit need to proxy to a very limited set of hosts, you can ad-hoc make a proxy mapping from local port to a single specific remote host. Just change the 8080, destination host, and buffer file.
And I mean this as encouragement, but you should read the synopsis from the GNU netcat manual. It's short and explains it well enough. netcat httpbin.org 80 is using connect mode, the -l one is using listen mode, and -p is --local-port (the port to listen on in listen mode).
- Our goal here is to match up to two network connections, so that anything we receive on one is forwarded to the other.
- netcat is a "networking swiss army knife", it's a tool for making network connections
- HTTP and bash pipelines both happen to be "line oriented", meaning they operate one line at a time. Exploiting this happy coincedence is what makes this work
- httpbin.org is a metanym for any website you need.
So:
echo "" > /tmp/buffer
Initialize our buffer to an empty state
tail -f /tmp/buffer
Read each line in the buffer, one at a time, and feed them to the next command. Do this forever (-f).
netcat httpbin.org 80
Connect to the website httpbin.org. Transmit each line from our buffer to the website.
netcat -lp 8080 > /tmp/buffer
Accept a connection. Transmit any data we receive from httpbin.org to this client; write any data we receive from the client to the buffer, so that it can be transmitted to httpbin.org.
B.) I don't think there is one! I've never needed to do this again in the, gosh, seven intervening years. Normally bash pipelines operate like a bucket brigade, moving data unidirectionally through various processing steps, and usually dumping it into a file at the end.
They aren't normally for creating long-lived programs that manage bidirectional flows of data. Which is why writing a web server in bash is a fun challenge.
I used to see value in scripts like this, but often these Bash scripts do quite the opposite. It may be readable for those that only know Bash, but you'll probably be happier if you learn Go or C. There are also a good amount of lightweight C web servers like darkhttpd.
Ha, this is more like a puzzle than anything - trying to implement a protocol in a highly restrictive environment.
For anyone looking for an easy way to spin up a command line web server, python has one built in, so if you are running linux usually you can do:
`python3 -m http.server`
From a terminal and it will spin up a web server that serves the current directory. I use this all the time for quick tinkering vs. installing/configuring nginx/node.js/caddy or whatever the cool kids are using these days.
The biggest issue with `http.server` is it's a blocking single-threaded server, so it can serve a single client at a time.
This makes it really really not great when trying to serve pages (with various attending static files), or to ad-hoc share large resources with other people on the network.
A single-threaded server is fine if you're only mildly IO bound, which is the case when you're serving files-as-resources. My understanding anyway is that http.server is explicitly not for production use!
> A single-threaded server is fine if you're only mildly IO bound, which is the case when you're serving files-as-resources.
It's fine if you're also evented, but that's not the case here, http.server literally can only interact with one connection at a time, the next connection(s) will be queued up waiting for http.server to finish its thing and accept them.
> My understanding anyway is that http.server is explicitly not for production use!
Sure, but "I'm at a lan party and have the installer for $game so we don't explode the 'net connection" is not the sort of "production use" that's intended by this statement.
Neither is "I want to open this HTML file I wrote which has a bunch of static assets booting up the webapp".
We had an engineer add it to his unit tests and started blaming the network team when everything started slowing down. Took me two days to convince him to try another server.
macOS 12 dropped PHP. It still has /usr/bin/python (CPython 2.7), but
- any app the uses it triggers a message box about the the developer needing to update their app
- before any REPL session it prints
"""
WARNING: Python 2.7 is not recommended.
This version is included in macOS for compatibility with legacy software.
Future versions of macOS will not include Python 2.7.
Instead, it is recommended that you transition to using 'python3' from within Terminal.
"""
There is no version of Python 3 included with the OS. The XCode app/developer tools include Python 3.8. I expect macOS 13 won't include any Python interpreter out the box
Getting python up an running on Windows is fairly trivial though. You don't even need local admin permissions if your machine is locked down by your IT dept.
If you have access to a busybox executable, it comes with a quite capable little httpd with /cgi-bin/ for CGI apps. If you have a wifi router, the web interface might be running off this :) (https://git.busybox.net/busybox/tree/networking/httpd.c)
Regarding the creation and removal of a tempfile (line 72 & 85):
local tmpFile="${TMPDIR:-/tmp/bash-web-server.$$}"
# ...
rm "$tmpFile"
To avoid collisions when the PID is reused, and to clean up0 the tempfile on errors, I recommend using mktemp and trap:
local tmpfile="$(mktemp --tmpdir="${TMPDIR:-/tmp}" bash-web-server.XXXXXX)"
trap "rm -f \"${tmpfile}\"" RETURN EXIT
# ...
# Do nothing at the end of the function; trap will
# remove the file at RETURN automatically.
Otherwise, I like the implementation! It's nice to see good bash techniques like parsing with "IFS='&' read -ra data" and rewriting variables with %%/etc.
To spare you the trap you can open the file as FD in your bash process (e.g., exec {my_fd}>"$TMPFILE") and then directly delete it before doing anything else, and use the /proc handle to access it ("/proc/$$/fd/${my_fd}")
It's also useful for signaling between processes to have them continue doing something as long as this proc path exists (the fd can also just be backed with >/dev/null instead of a real file that needs to be removed)
Normally, the pid and fd could be reused, so you should either make it a known unique path and verify it's still the same, lock the pid via some means (not sure if and how it's possible), or use another technique.
One more fun experiment: avoid the tempfile on disk/tmpfs by storing data into the pipe connecting two processes, where the first one can already exit for the pipe to give EOF when reading beyond the buffered data.
Since now, multiple concurrent connections will work after my patch. (Need to add subshell) i will need to consider using fd as output cache. Instead of temporary files.
This here is a simple echo server that uses tr to uppercase:
exec {checkfd}>/dev/null
CHECKFDPATH="/proc/$$/fd/${checkfd}"
(while [ -e "$CHECKFDPATH" ]; do sleep 1; done) > >(true) &
STDINPID="$!"
WRITER="/proc/$STDINPID/fd/1"
while IFS= read -r LINE; do
# only echo lines if we didn't close the connection yet
if [ -e "$CHECKFDPATH" ]; then
echo "$LINE" | tr '[:lower:]' '[:upper:]' > "$WRITER"
if [ "$LINE" = "bye" ]; then
exec {checkfd}<&-
fi
else
echo "received while closed: $LINE"
fi
done < <(nc -q 1 -l 8080 < "$WRITER")
if [ -e "$CHECKFDPATH" ]; then
# close checkfd to close writer pipe
exec {checkfd}<&-
fi
The fd will only be used to stor it temporary since, we first need to sent headers, and the headers need to be sent before the bofy. The server base os working fine now with accept and a patch request os already sent to bash. So probaböy on the next release accept will be full usable.
The other day I wrote this bash code after getting frustrated with the password generators available to me, just so I had something to use right in the shell:
It actually took an entire afternoon and went through a couple iterations, and I hadn't even written a test for it yet (and it just so happens that I started an MVP shell-script testing library for just such an occasion when I ALSO got frustrated with the bash-accessible testing libraries available to me, also in Bash: https://github.com/pmarreck/tinytestlib)
But then I immediately felt bad. I could have written all of this in less than an hour in a language like Elixir, with test coverage (and the testing/assertion lib is built-in, so it would have been free).
It is impressive and code-golfish that someone can "write a webserver in pure Bash" but Bash is a crap language to script in at this point, and adding more Bash code to the world just seems wrong now. Bourne shells were invented in the 70's (!), long before many language advancements and understandings, and they thus have MANY warts (many of which we're familiar with).
Bash is not mean for kind of projects like this. I even develop on other languages. But i like bash. A good bash script, is like reading a novel, because it's challenging to have a clean script.
I get it. The challenging part is fun. You can learn all the corner cases, the clever bash-isms, and then show off. But the code you end up with is essentially unmaintainable, especially since you don't have any test coverage, so...
Well that's true. I took me two hours to get this done. Since accept was not clear at the beginning. But it will be cleaner in the future, i'm still not convinced how it works currently.
> and it just so happens that I started an MVP shell-script testing library for just such an occasion when I ALSO got frustrated with the bash-accessible testing libraries available to me
I found Bats straightforward and simple to use, never felt the need to reinvent the wheel there.
2) doesn't use a very nice syntax (hint: bashisms are not a very nice syntax). Compare BATS assertions with this for example (the test of my test library, which uses the library's own functions): https://github.com/pmarreck/tinytestlib/blob/yolo/test Call me crazy but I think `assert_equal "4" "$result"` is a lot more readable than `[ "$result" -eq 4 ]`
3) doesn't let you assert on all of (return code, stdout, stderr) from running a single command. I created a `capture` function to do that and bring those all into named variables that you can then assert on.
But overall I wanted something tiny that you could easily include alongside a bash script instead of establishing as a new dependency you'd have to install separately. Honestly, I feel that functions like assert_equal, assert_not_equal, assert_success, assert_failure, assert_match and assert_no_match (which all raise on failure) should be built into every programming language's standard library.
I know that you can sort of get by just using `set -e` and just do a comparison operation, but...
Sort of related, if you do "man read" you the the huge "bash" man page. It would be nice if man could be improved to take you to the "read" command within that page. Or a new command "manbash read"
Thanks I didn't know! So many years of searching for "read" (etc) in the huge bash man page and, of course, in the case of "read" finding many false positives. Still it would be nice if `man` could be improved to know about builtin commands - the obvious thing is something like an html anchor in the big bash man page for each built in.
I had this same idea, but just haven't gotten around to implementing it - so thank you for this reference! This also gives me the perfect excuse for finally getting around to implement automatic dynamic loading of Bash builtins for my Bash package manager
This would be very useful as a fallback - in case something like `socat` isn't installed, this can be used to display some sort of dumbed down version of the site
I follow some projects from you on github. Keep up the work!
Falling back to something like socat, would destroy the whole idea behind it. I think the best way is patching accept and shipping it with the project. Like this no need to fallback.
And considering using something like socat, will force you to fork each request into a new process, which will be slow as hell.
I worked before on a web framework, which was used behind httpd or nginx as cgi, it was fast. But the moment where you get more connections it was slow as hell.
The idea of this project is writing some kind of fastCGI (like php-fpm), but for this i would need, to allow multithreading etc..
98 comments
[ 2.7 ms ] story [ 151 ms ] threadThere was a post a bit ago that had a one liner for a basic web server using net cat:
I always use the pipe, it is intuitive to me to think left-to-right, output of this becomes input of next. But I always see stackoverflow answers using the other approach.
In the second block, you're just launching one program that reads input, not two. It's slightly more efficient.
I also tend to build large *NIX pipelines using `cat $foo | [...]`, but it's some times regarded as bad form. See: "useless use of cat" for more examples.
Maybe it's lighter than cat, but whatever code path is triggered by the `<` can't be zero-cost
"loadable accept builtin"
Ahh.
Gawk can be a web server also, typically just "as-shipped", without any new extensions. Doesn't scale well though :)
https://news.ycombinator.com/item?id=22085459
The /inet/tcp/8080/0/0 is a gawk-ism, versus a Linux thing:
https://www.gnu.org/software/gawk/manual/html_node/TCP_002fI...
Unfortunately, gawk doesn't let you control the listen/accept part of it, so it doesn't scale well. Not sure why they did it that way. A separate accept() call would have made it actually usable in a non-toy way.
This is how you get stuff like hiPHoP.
Definitely closest to the mark though, kudos!
Regarding keeping the file, if memory serves the "real" pipeline had some `tee`s in it. I recreated this much more recently to make a GitHub repo of funny code snippets (https://github.com/MaxBondABE/one-thread-crashing/).
You reminded me of a different toy I made many years ago. I created FIFOs for files that you might try to read when exploiting an arbitrary file read in a web app, say /etc/www/apache.conf (if that's the right path - it's been a while since I've configured a web server!) and I'd have a program that opened them. This would block until someone opened the other end of the FIFO, at which point I'd raise an alarm (which was just a print statement).
1) Create a temp buffer file
2) Read the file constantly to STDOUT?
3) What does netcat httpbin.org 80 | netcat -lp 8080 do?
What would be a good use of this circular pipeline? Can you give me some example of what you use this for? Thanks
And I mean this as encouragement, but you should read the synopsis from the GNU netcat manual. It's short and explains it well enough. netcat httpbin.org 80 is using connect mode, the -l one is using listen mode, and -p is --local-port (the port to listen on in listen mode).
A.)
Some context:
- Our goal here is to match up to two network connections, so that anything we receive on one is forwarded to the other.
- netcat is a "networking swiss army knife", it's a tool for making network connections
- HTTP and bash pipelines both happen to be "line oriented", meaning they operate one line at a time. Exploiting this happy coincedence is what makes this work
- httpbin.org is a metanym for any website you need.
So:
Initialize our buffer to an empty state Read each line in the buffer, one at a time, and feed them to the next command. Do this forever (-f). Connect to the website httpbin.org. Transmit each line from our buffer to the website. Accept a connection. Transmit any data we receive from httpbin.org to this client; write any data we receive from the client to the buffer, so that it can be transmitted to httpbin.org.B.) I don't think there is one! I've never needed to do this again in the, gosh, seven intervening years. Normally bash pipelines operate like a bucket brigade, moving data unidirectionally through various processing steps, and usually dumping it into a file at the end.
They aren't normally for creating long-lived programs that manage bidirectional flows of data. Which is why writing a web server in bash is a fun challenge.
For anyone looking for an easy way to spin up a command line web server, python has one built in, so if you are running linux usually you can do:
`python3 -m http.server`
From a terminal and it will spin up a web server that serves the current directory. I use this all the time for quick tinkering vs. installing/configuring nginx/node.js/caddy or whatever the cool kids are using these days.
This makes it really really not great when trying to serve pages (with various attending static files), or to ad-hoc share large resources with other people on the network.
It's fine if you're also evented, but that's not the case here, http.server literally can only interact with one connection at a time, the next connection(s) will be queued up waiting for http.server to finish its thing and accept them.
> My understanding anyway is that http.server is explicitly not for production use!
Sure, but "I'm at a lan party and have the installer for $game so we don't explode the 'net connection" is not the sort of "production use" that's intended by this statement.
Neither is "I want to open this HTML file I wrote which has a bunch of static assets booting up the webapp".
Looking at you HttpClient-on-esp8266...
I think I see your problem.
https://stackoverflow.com/questions/12905426/what-is-a-faste...
Also, the python solution is only relevant if python is already installed. Something that's not true on Windows and is or will soon be true on MacOS.
""" WARNING: Python 2.7 is not recommended. This version is included in macOS for compatibility with legacy software. Future versions of macOS will not include Python 2.7. Instead, it is recommended that you transition to using 'python3' from within Terminal. """
There is no version of Python 3 included with the OS. The XCode app/developer tools include Python 3.8. I expect macOS 13 won't include any Python interpreter out the box
Getting python up an running on Windows is fairly trivial though. You don't even need local admin permissions if your machine is locked down by your IT dept.
But in this script i would like to avoid external commands. I will even remove the use of tmp files kn thw future.
The fd will only be used to stor it temporary since, we first need to sent headers, and the headers need to be sent before the bofy. The server base os working fine now with accept and a patch request os already sent to bash. So probaböy on the next release accept will be full usable.
https://gist.github.com/pmarreck/f4dbb02396c4532762a7a64511c...
It actually took an entire afternoon and went through a couple iterations, and I hadn't even written a test for it yet (and it just so happens that I started an MVP shell-script testing library for just such an occasion when I ALSO got frustrated with the bash-accessible testing libraries available to me, also in Bash: https://github.com/pmarreck/tinytestlib)
But then I immediately felt bad. I could have written all of this in less than an hour in a language like Elixir, with test coverage (and the testing/assertion lib is built-in, so it would have been free).
It is impressive and code-golfish that someone can "write a webserver in pure Bash" but Bash is a crap language to script in at this point, and adding more Bash code to the world just seems wrong now. Bourne shells were invented in the 70's (!), long before many language advancements and understandings, and they thus have MANY warts (many of which we're familiar with).
The next time I feel the urge to code anything more than a one-liner in Bash, I'm writing it in elixir script https://thinkingelixir.com/2019-04-running-an-elixir-file-as... and just sucking up the extra run time (or compiling it into an executable with something like https://github.com/spawnfest/bakeware/ and sucking up the extra megabytes), and then MAYBE writing a wrapper function for my .profile
Incidentally, if you DO want a shell that "tastes like" Bourne but is functional and thus sucks a lot less, check out https://github.com/wryun/es-shell, or my fork of it https://github.com/pmarreck/es-shell/
...it feels like "if it's possible to do in bash, this does it".
I found Bats straightforward and simple to use, never felt the need to reinvent the wheel there.
1) bloated
2) doesn't use a very nice syntax (hint: bashisms are not a very nice syntax). Compare BATS assertions with this for example (the test of my test library, which uses the library's own functions): https://github.com/pmarreck/tinytestlib/blob/yolo/test Call me crazy but I think `assert_equal "4" "$result"` is a lot more readable than `[ "$result" -eq 4 ]`
3) doesn't let you assert on all of (return code, stdout, stderr) from running a single command. I created a `capture` function to do that and bring those all into named variables that you can then assert on.
But overall I wanted something tiny that you could easily include alongside a bash script instead of establishing as a new dependency you'd have to install separately. Honestly, I feel that functions like assert_equal, assert_not_equal, assert_success, assert_failure, assert_match and assert_no_match (which all raise on failure) should be built into every programming language's standard library.
I know that you can sort of get by just using `set -e` and just do a comparison operation, but...
https://github.com/russellballestrini/bash-kira/blob/master/...
Kira (bash-kira): a small bash script for killing programs which run too long.
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/t...
https://duckduckgo.com/?q="while+%3A%3B+do"+bash
Example : help read
This would be very useful as a fallback - in case something like `socat` isn't installed, this can be used to display some sort of dumbed down version of the site
Falling back to something like socat, would destroy the whole idea behind it. I think the best way is patching accept and shipping it with the project. Like this no need to fallback.
And considering using something like socat, will force you to fork each request into a new process, which will be slow as hell. I worked before on a web framework, which was used behind httpd or nginx as cgi, it was fast. But the moment where you get more connections it was slow as hell.
The idea of this project is writing some kind of fastCGI (like php-fpm), but for this i would need, to allow multithreading etc..