A Web Server in 1 Line of Bash Code

For people who do a lot of work with command line tools or Bash code, having a Bash web server could be very handy.

I was really amazed that in one line of Bash code I was able to create web servers that could:

  • send the output from a bash command directly to a browser page
  • create diagnostic pages using standard Linux tools
  • create pages that view Rasp PI GPIO pins
  • create a page to toggle a PI GPIO pin

One Line Web Servers

There are number of 1 line web servers, these are minimal offerings that exist in most of the programming languages.

A Bash web server can be created using the nc or netcat, the networking utility:

while true; do { \
  echo -ne "HTTP/1.0 200 OK\r\nContent-Length: $(wc -c <index.htm)\r\n\r\n"; \
  cat index.htm; } | nc -l -p 8080 ; \ 
done

This Bash statement echo’s to port 8080, the output is an HTTP header with the file content length defined. The cat command is used to show the HTML file.

This 1 line Bash example shows a single page (index.htm) which isn’t overly useful, there are other web server options that would work much better.

Where a Bash web server really stands out is in its ability to execute command line utilities or scripts and send the results to a web client.

Bash Web Server Calling Bash Commands

The output from command line utilities like iostat can be sent directly to a web client:

while true;
  do echo -e "HTTP/1.1 200 OK\n\n$(iostat)" \
  | nc -l -k -p 8080 -q 1; 
done

There are 2 important options that need to be set on nc, and they are: -k (this keeps the connection open after the first connection) and -q 1 (this closes the connection after 1 seconds, so another connection can occur). Depending on the complexity of the script that is used the -q timing may need to be adjusted.

The web page for the iostat command will look like:

Multiple Commands with Headings

Comments and multiply command line utilities can be defined as a variable that can be passed to the Bash Web server.

The figlet utility can be used to create custom ASCII headings, this is useful if you want to keep things simple without using HTML syntax. To install figlet in Ubuntu enter: sudo apt-get install figlet .

An example of using figlet headings and the sensors and vmstat utility is:

title1=$(figlet Sensors)
cmd1=$(sensors | sed -e 's/\°/ /g') # browser has problem with degrees, so remove
title2=$(figlet VMStat)
cmd2=$(vmstat)
thebody="$title1\n$cmd1\n$title2\n$cmd2"

while true;
  do echo -e "HTTP/1.1 200 OK\n\n$thebody" \
  | nc -l -p 8080 -q 1; 
done

Bash Web Server with Raspberry Pi GPIO

For many Raspberry Pi projects monitoring the status of the GPIO (General Purpose Input/Output) pins is quite important.

The Raspberry Pi gpio utility can be used to show the present status with the readall option:

pi@raspberrypi:~/pete $ gpio readall
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 | ALT0 | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 | ALT0 | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |  OUT | 0 |  7 || 8  | 0 | IN   | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | IN   | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |  OUT | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI | ALT0 | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO | ALT0 | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK | ALT0 | 0 | 23 || 24 | 1 | OUT  | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | OUT  | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |  OUT | 0 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |  OUT | 0 | 31 || 32 | 0 | OUT  | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |  OUT | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |  OUT | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |  OUT | 0 | 37 || 38 | 0 | OUT  | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | OUT  | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+

Rather than passing the Bash commands as a string, a small Bash script (web_body.sh) file can be created. This file will show the time and then call the gpio readall command.

#!/bin/bash
# web_body.sh - Show the time and PI GPIO pins

date $T

echo "$(gpio readall)"

The online Bash web server command is:

while true; do { \
  echo -ne "HTTP/1.1 200 OK\r\n"; sh web_body.sh; } \
  | nc -l -k -q 2 8080; \
done

The Web page for this script will look something like:

Send GPIO Writes from the Address Bar

Client side GET requests can be simulated on the browser address bar.

For example entering gpio write 7 1 on the address bar sents the Bash Server a GET request.

The HTTP request encodes spaces, so a space appears as a %20.

Bash code can be added to look for specific messages. In this case the “gpio write 7 1” or “gpio write 7 0” message can be search for, and if it is found then that exact message can be executed.

The Bash code can now be modified to ; look for the “GET gpio” message, then decode any HTTP %20 characters to spaces, next parse out the string to get the gpio message, and finally execute the required command. The code is below:

while true; 
  do { echo -ne "HTTP/1.1 200 OK\r\n"; sh web_body.sh; } | \
  nc -l -k -q 5 8080 | grep "GET /gpio" | \
  sed -e 's/%20/ /g' | \
  eval $( awk '{print substr($0,6,15) }') ;
done

With the new code, the gpio write is executed and the result can be seen in the web page.

Create an HTML Form

Entering commands on the command line works but it’s crude, a better way is to create an HTML Form.

The Bash web server code can remain exact the same as in the earlier example.

The original script (web_body.sh) file can be modified to made the output HTML format and three forms can be included. The first and second form will define the GET actions to turn on or off the GPIO pin and the third form will be used to refresh the page to check for GPIO changes.

#!/bin/bash
# web_body.sh - Show the time and PI GPIO pins
#             - Use HTML instead of text output
#             - Add forms for GPIO on/off, and a refresh
echo "
<!DOCTYPE html><html><head>
</head><body>
<h1>Bash Commands in a Web Page</h1>
<h2>Toggle Pin 7 On/Off</h2>
<form action='gpio write 7 0'>
 <input type='submit' value='OFF'> 
</form> 
<form action='gpio write 7 1'>
 <input type='submit' value='ON'>
</form>
<form action=''>
 <input type='submit' value='Refresh Page'>
</form>
<pre>
"

date $T

echo "$(gpio readall)"

echo "</pre></body></html>"

The client web page is now:

After turning on or off the GPIO pin, a refresh of the web page is required to see the new status.

Final Commands

A Bash Web Server is a quick and easy solution for viewing the output from Bash scripts and commands. I especially like the fact that I don’t need to install any special software and I don’t need to write any HTML code.

It is important to note that the number of concurrent connections is very low, (1/sec if the nc -q option is 1).

A Bash Web Server supports client side GET and POST requests, however for complex requirements the Bash code could start to get messy quickly and it would probably be best to look at another solution.

7 thoughts on “A Web Server in 1 Line of Bash Code

    1. Very nice… except the result is actually prepared and sent BEFORE any client even attempts to connect! Technically we should listen and read the request, then send the result at the time of the request.

      Like

      1. The toggling of GPIO pins is an example where the request is read and then a result is generated.

        This is also a huge security risk, because you could write Bash web server scripts that would allow you to run externally generated commands.

        Like

    1. I believe that you had typo in your comment and you mean \r\n and NOT /r/n.

      True a \r doesn’t mean anything within in an HTML or <body. tag, but for Linux commands a \r (a carriage return) is a meaningful character. In the starting examples I was showing the use of Linux commands being send out directly as is.

      If you look at any of the earlier threads on this topic you'll see that a \r\n is used. (https://stackoverflow.com/questions/6686261/what-at-the-bare-minimum-is-required-for-an-http-request). I tried to keep to this standard.

      Like

Leave a comment