echo $(echo 66 85 84 84 83 | awk '{for(i=1;i<=NF;i++) printf "%c", $i}')
Tag: scripting
-

Bash Script: Calculate before/after 2: Calculate Harder
As an update, or an evolution of my earlier script that did some simple math for me, I’ve made one that will full-on test a URL while I’m making changes to see what the impact performance is of my updates.
$ abtesturl.sh --url=https://example.com/ --count=10 Press any key to run initial tests... Initial average TTFB: 3.538 seconds Press any key to re-run tests... Running second test... Second average TTFB: 1.975 seconds Before TTFB: 3.538 seconds After TTFB: 1.975 seconds Change in TTFB: -1.563 seconds Percentage Change: -44.00%Code language: JavaScript (javascript)It makes it much simpler to gather data to write reports or figure out of a change is worth the effort.
Well, that’s about it so here’s the script:
#!/bin/bash function show_usage() { echo "Usage: $0 --url=<URL> [--count=<number of requests>]" echo " --url Specifies the URL to test." echo " --count Optional. Specifies the number of requests to send. Default is 6." echo echo "Example: $0 --url=https://example.com/ --count=5" exit } function average_ttfb() { local URL="" local COUNT=6 # Default COUNT to 6 if not supplied local CURL_OPTS="-s" # Parse arguments for arg in "$@"; do case $arg in --url=*) URL="${arg#*=}" shift # Remove argument from processing ;; --count=*) COUNT="${arg#*=}" shift # Remove argument from processing ;; *) # Unknown option ;; esac done if [[ -z "$URL" ]]; then exit 1 fi local total_time=0 local count_success=0 for ((i=1; i<=COUNT; i++)) do # Perform the curl command, extracting the time to first byte ttfb=$(curl $CURL_OPTS -o /dev/null -w "%{time_starttransfer}\n" $URL) # Check if the curl command was successful if [ $? -eq 0 ]; then total_time=$(echo "$total_time + $ttfb" | bc) ((count_success++)) else echo "Request $i failed." >&2 fi done if [ $count_success -eq 0 ]; then echo "All requests failed." >&2 return 1 fi # Calculate the average TTFB average_time=$(echo "scale=3; $total_time / $count_success" | bc) echo $average_time # This line now simply outputs the average time } function ab_test_ttfb() { # Run initial test read -p "Press any key to run initial tests..." -n 1 -r initial_ttfb=$(set -e; average_ttfb "$@"; set +e) echo "Initial average TTFB: $initial_ttfb seconds" # Wait for user input to proceed read -p "Press any key to re-run tests..." -n 1 -r echo # Move to a new line # Run second test echo "Running second test..." second_ttfb=$(average_ttfb "$@") echo "Second average TTFB: $second_ttfb seconds" # Calculate and output the difference and percentage change difference=$(echo "$second_ttfb - $initial_ttfb" | bc) percent_change=$(echo "scale=2; ($difference / $initial_ttfb) * 100" | bc) echo "Before TTFB: $initial_ttfb seconds" echo "After TTFB: $second_ttfb seconds" echo "Change in TTFB: $difference seconds" echo "Percentage Change: $percent_change%" } # Check if help is requested or no arguments are provided if [[ " $* " == *" --help "* ]] || [[ "$#" -eq 0 ]]; then show_usage fi # Check if --url is in the arguments url_present=false for arg in "$@"; do if [[ $arg == --url=* ]]; then url_present=true break fi done if [ "$url_present" = false ]; then echo "Error: --url argument is required." show_usage fi ab_test_ttfb "$@"Code language: Bash (bash)Don’t break anything!
-

Matrix Reimagined: Crafting Digital Rain with Bash and ChatGPT
Just for fun, and I have no idea why I thought about it, I decided to work with ChatGPT (4) to build a simple bash-based version of the Matrix Digital Rain. I know there’s already better versions, like cmatrix, but we do not do things because they are easy. We do them because we are bored.

I’ve asked ChatGPT to heavily comment the code for us so that we can see exactly what’s going on:
#!/bin/bash # This script creates a Matrix-style falling text effect in the terminal. # Define strings for extra characters (Japanese Katakana) and extended ASCII characters extra_chars="カキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン" extended_ascii="│┤┐└┴┬├─┼┘┌≡" # Define arrays of color codes for a fading green color effect, and a static color fade_colors=('\033[38;2;0;255;0m' '\033[38;2;0;192;0m' '\033[38;2;0;128;0m' '\033[38;2;0;64;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;32;0m' '\033[38;2;0;16;0m' '\033[38;2;0;8;0m') # Fading green colors static_color='\033[38;2;0;0;0m' # Static dark green color white_bold='\033[1;37m' # White and bold for the primary character # Get terminal dimensions COLUMNS=$(tput cols) # Number of columns in the terminal ROWS=$(tput lines) # Number of rows in the terminal # Hide the cursor for a cleaner effect and clear the screen echo -ne '\033[?25l' clear # Function to generate a random character from the set of extra characters and extended ASCII random_char() { local chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789${extra_chars}${extended_ascii}" echo -n "${chars:RANDOM%${#chars}:1}" } # Generate a list of 1000 random characters random_chars="" for (( i=0; i<1000; i++ )); do random_chars+=$(random_char) # Add a random character to the end of the string done # Initialize a counter for cycling through the random characters char_counter=0 # Counter for cycling through the random characters # Initialize arrays to keep track of the position and trail characters of each column positions=() # Array to store the current position in each column trail_chars=() # Array to store the trail characters in each column for (( c=1; c<=COLUMNS; c++ )); do positions[$c]=$((RANDOM % ROWS)) # Random starting position for each column trail_chars[$c]="" # Start with an empty trail for each column done # Function to update the display with the falling text effect update_line() { local last_pos=0 # Track the last position to optimize cursor movement for (( c=1; c<=COLUMNS; c++ )); do # Randomly skip updating some columns to create a dynamic effect if [ $((RANDOM % 4)) -ne 0 ]; then continue fi local new_char=${random_chars:$char_counter:1} # Select the next character from the random string char_counter=$(( (char_counter + 1) % 1000 )) # Update the counter, cycling back after 1000 local pos=${positions[$c]} # Current position in this column local trail=${trail_chars[$c]} # Current trail of characters in this column trail_chars[$c]="${new_char}${trail:0:$((ROWS - 1))}" # Update the trail by adding new character at the top # Render the trail of characters for (( i=0; i<${#trail}; i++ )); do local trail_pos=$((pos - i)) # Calculate the position for each character in the trail if [ $trail_pos -ge 0 ] && [ $trail_pos -lt $ROWS ]; then local color=${fade_colors[i]:-$static_color} # Choose color from the fade array or static color if beyond the array if [ $i -eq 0 ]; then color=$white_bold # First character in the trail is white and bold fi if [ $last_pos -ne $trail_pos ]; then printf "%b" "\033[${trail_pos};${c}H" # Move cursor to the right position last_pos=$trail_pos fi printf "%b" "${color}${trail:$i:1}\033[0m" # Print the character with color fi done positions[$c]=$((pos + 1)) # Update the position for the next cycle if [ $pos -ge $((ROWS + ${#fade_colors[@]})) ]; then positions[$c]=0 # Reset position if it moves off screen trail_chars[$c]="" fi done } # Main loop for continuous execution of the update_line function while true; do update_line done # Reset terminal settings on exit (show cursor, clear screen, reset text format) echo -ne '\033[?25h' # Show cursor clear tput sgr0 # Reset text formatCode language: PHP (php)Challenges Faced
Developing the Matrix Digital Rain script presented specific challenges, especially in terms of performance. The initial use of
tputfor cursor manipulation proved inefficient for the dynamic text display. This issue was resolved by switching toprintfand ANSI escape sequences, which significantly enhanced the rendering performance.Another problem arose with the use of fullwidth Katakana characters, which were incompatible with monospaced fonts, disrupting the visual flow. The solution involved adopting halfwidth Katakana, ensuring better compatibility and preserving the uniformity essential for the Matrix-style effect.
Another notable challenge emerged in development: the inefficiency of generating random characters on the fly. Due to Bash’s slower handling of string functions, this method significantly hindered performance. To tackle this, a strategic shift was made from real-time character generation to utilizing a predefined lookup table.
This approach involved generating a large set of pseudorandom characters before entering the main loop of the program. By doing so, I could rapidly access this table during runtime, boosting performance. This change played a crucial role in maintaining fluidity and responsiveness. It also preserved the illusion of randomness, essential for the authentic Matrix effect, thus striking a balance between performance efficiency and visual fidelity.
If you’d like to see what it looks like, here’s an example:
So yeah. That’s it I guess? Enjoy!
-

Quick Tip: Add Screen Name to Bash Prompt
I often SSH into servers to get some work done, and one of the things I discovered recently is that I may not always know or remember if I’m in a screen session.
So I had the bright idea to just add it to my shell prompt!

Simply just add one of these to your RC file of choice:
Bash
# Add Screen name to PS1 if we're in a screen. if [ -n "$STY" ]; then PS1="\[\e[1m\](Screen: $STY)\[\e[0m\]\n$PS1" fiCode language: PHP (php)ZSH
# Add Screen name to PROMPT if we're in a screen. if [[ -n "$STY" ]]; then PROMPT="%B(Screen: $STY)%b"$'\n'"$PROMPT" fiCode language: PHP (php)And remember, if you’re asking yourself if you should run something in a screen, you’re already too late!
-

Bash Script: Calculate before/after averages
I’ve been doing some performance testing, and wanted a quick way to test how well or poorly changes affect a site. Normally I’d whip out the ol’ calculator app and do this manually. That got tiring after a while, so instead with the help of ChatGPT, I made this little bash script that will do the work for you:
#!/bin/bash # Function to calculate the average of a list of numbers average() { local sum=0 local count=0 for num in "$@"; do sum=$(echo "$sum + $num" | bc -l) count=$((count+1)) done echo "$sum / $count" | bc -l } # Parse arguments for i in "$@"; do case $i in --before=*) BEFORE="${i#*=}" shift ;; --after=*) AFTER="${i#*=}" shift ;; *) # unknown option ;; esac done # Check if both BEFORE and AFTER parameters are provided if [ -z "$BEFORE" ] || [ -z "$AFTER" ]; then echo "Error: Missing required parameters." echo "Usage: $0 --before=<comma-separated-values> --after=<comma-separated-values>" exit 1 fi IFS=',' read -ra BEFORE_LIST <<< "$BEFORE" IFS=',' read -ra AFTER_LIST <<< "$AFTER" # Calculate average for before and after lists BEFORE_AVG=$(printf "%.2f\n" $(average "${BEFORE_LIST[@]}")) AFTER_AVG=$(printf "%.2f\n" $(average "${AFTER_LIST[@]}")) echo "Before average: $BEFORE_AVG" echo "After average: $AFTER_AVG" # Calculate average percent increase, decrease or no change for the list if [ "$BEFORE_AVG" != "0.00" ]; then PERCENT_CHANGE=$(echo "(($AFTER_AVG - $BEFORE_AVG) / $BEFORE_AVG) * 100" | bc -l) if [ "$(echo "$PERCENT_CHANGE > 0" | bc -l)" -eq 1 ]; then printf "Average percent increased: %.2f%%\n" "$PERCENT_CHANGE" elif [ "$(echo "$PERCENT_CHANGE < 0" | bc -l)" -eq 1 ]; then printf "Average percent decreased: %.2f%%\n" "$PERCENT_CHANGE" | tr -d '-' else echo "No change in average." fi else echo "Percent change from before to after: undefined (division by zero)" fiCode language: Bash (bash)It runs like this:
$ average.sh --before=13.07,9.75,16.14,7.71,10.32 --after=1.22,1.28,1.13,1.19,1.26 Before average: 11.40 After average: 1.22 Average percent decreased: 89.30%In this instance, it was calculating seconds–but you need to remember that it only goes to two decimal places, so if you need something finer you’ll need to adjust the code or your inputs.
Happy Slacking!
-

Sending Prowl Alerts via Bash
I’m working on some server scripting and I wanted to find a way to make sure I get proper alerts. I tried hard getting Twilio working cheaply, but that failed. So I remembered that I use Prowl for other things on my phone–so why not just send more alerts that way?
So, again with the help of ChatGPT I have made a terrible monstrosity:
#!/bin/bash # Exit if any command fails, if an unset variable is used, or if a command in a pipeline fails set -euo pipefail # Default values application="Shell Notification" priority=0 event="Event" message="" logfile="/var/log/send-prowl-alert.log" url="" # Usage information usage() { echo "Usage: $0 --message=<message> [--application=<application>] [--event=<event>] [--description=<description>] [--priority=<priority>] [--url=<url>]" echo "Required:" echo " --message=<message> A description of the event, generally terse" echo " Maximum of 10000 bytes" echo "Optional:" echo " --application=<application> The name of the application (default: 'Shell Notification')" echo " Maximum of 256 bytes" echo " --event=<event> The name of the event or subject of the notification (default: 'Event')" echo " Maximum of 1024 bytes" echo " --priority=<priority> The priority of the alert (default: 0)" echo " An integer value ranging [-2, 2] representing:" echo " -2 Very Low" echo " -1 Moderate" echo " 0 Normal" echo " 1 High" echo " 2 Emergency" echo " Emergency priority messages may bypass quiet hours according to the user's settings." echo " --url=<url> The URL which should be attached to the notification" echo " Maximum of 512 bytes" echo " --help Displays this help message" } # URL Encodes a string urlencode() { local string="$1" local length="${#string}" local i for ((i = 0; i < length; i++)); do local c="${string:i:1}" case $c in [a-zA-Z0-9.~_-]) printf '%s' "$c" ;; *) printf '%%%02X' "'$c" ;; esac done } # Check if the script is being run manually or via cron if [[ -t 1 ]]; then # Output to console if being run manually log_message() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } else # Output to log file if being run via cron log_message() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "${logfile}" # Truncate the log file to 1 megabyte if [[ $(wc -c <"${logfile}") -gt 1000000 ]]; then tail -c 1000000 "${logfile}" > "${logfile}.tmp" && mv "${logfile}.tmp" "${logfile}" fi } fi # Parse arguments for arg in "$@"; do case "${arg}" in --message=*) message=${arg#*=} ;; --application=*) application=${arg#*=} ;; --event=*) event=${arg#*=} ;; --priority=*) priority=${arg#*=} ;; --url=*) url=${arg#*=} ;; --help) usage exit 0 ;; *) echo "Error: Unsupported argument ${arg}" usage exit 1 ;; esac done # Create a variable to store error messages errors="" # Check if curl is installed if ! command -v curl &> /dev/null; then errors+="Error: curl is not installed. Please install curl and try again.\n" fi # Check that a message was provided if [[ -z "${message}" ]]; then errors+="Error: No message provided." fi # Check if the URL is valid if [[ -n $url && ! $url =~ ^(https?://)[^\s/$.?#].[^\s]*$ ]]; then errors+="Error: Invalid URL.\n" fi # Check byte size of the parameters and append error if they exceed the max limit if [[ "${#url}" -gt 512 ]]; then errors+="Error: URL exceeds maximum byte limit (512 bytes).\n" fi if [[ "${#application}" -gt 256 ]]; then errors+="Error: Application name exceeds maximum byte limit (256 bytes).\n" fi if [[ "${#event}" -gt 1024 ]]; then errors+="Error: Event name exceeds maximum byte limit (1024 bytes).\n" fi if [[ "${#message}" -gt 10000 ]]; then errors+="Error: Message exceeds maximum length (10000 bytes).\n" fi if [[ ! "${priority}" =~ ^-?[0-2]$ ]]; then errors+="Error: Invalid priority. Must be an integer between -2 and 2.\n" fi # Check if the API key file exists and is not empty if [[ ! -s ~/.prowl_api_key ]]; then errors+="Error: The Prowl API key file does not exist or is empty. Please create a file at ~/.prowl_api_key and add your API key to it.\nExample:\n echo 'your_api_key' > ~/.prowl_api_key\n chmod 600 ~/.prowl_api_key\n" fi # Check the permissions on the API key file, if it exists # We have to do it in a convoluted cross platform way because macos does not support `stat -c` if [[ -f ~/.prowl_api_key ]]; then # Retrieve the file permissions and remove any trailing '@' symbol (indicating extended attributes) permissions=$(ls -l -d ~/.prowl_api_key | awk '{print $1}' | tr -d '@') # Check if the permissions are not set to '-rw-------' if [[ "$permissions" != "-rw-------" ]]; then # Add an error message to the 'errors' variable errors+="Error: The permissions on the API key file are not set correctly. Please run 'chmod 600 ~/.prowl_api_key' and try again.\n" fi fi # If any errors were detected, print the error messages and exit if [[ -n $errors ]]; then echo -e "${errors}" # -e enables interpretation of backslash escapes like \n exit 1 fi # Get the Prowl API key prowl_api_key=$(cat ~/.prowl_api_key) # Save the alert data for logging before we encode it alert_log_string="$application : $event : $priority : $url : $message" # URL-encode the variables prowl_api_key=$(urlencode "$prowl_api_key") application=$(urlencode "$application") event=$(urlencode "$event") message=$(urlencode "$message") priority=$(urlencode "$priority") url=$(urlencode "$url") # Send the alert response=$(curl -s "https://api.prowlapp.com/publicapi/add?apikey=${prowl_api_key}&application=${application}&event=${event}&description=${message}&priority=${priority}&url=${url}") # Check if the alert was sent successfully if [[ "${response}" != *"success code=\"200\""* ]]; then log_message "Error sending alert: ${alert_log_string}" log_message "Response: ${response}" exit 1 fi log_message "Alert sent successfully: $alert_log_string" exit 0Code language: PHP (php)Good luck!
-

Supercharge Your Clipboard with a Shell Function for iTerm2
Well, I’ve been playing with ChatGPT more, and I had ChatGPT (GPT-4) write this blog post for me. I told it that I was going to give it some of my blog posts to determine my writing style, and to have it write me a blog post about this bash function. It’s …. meh … not really “my style” but whatever.
The idea of this shell function is to integrate with iTerm2’s proprietary escape sequences to allow you to pipe content right to your clipboard. I’m using the name
pbcopyfrom MacOS so that I don’t need to think about it, and can usepbcopyanywhere. Now I can work on a remote server andcat stupid-stuff.log | pbcopyand get a copy of it locally to mess with, without having to click and drag or download the file. Super lazy ❇Enjoy the AI blog post:
Hey folks, today we’re going to explore a neat shell function that can make your life a lot easier when you’re working in iTerm2 on macOS. The purpose of this function is to make the built-in
pbcopycommand work seamlessly in iTerm2, even when the command isn’t available in your shell environment.Without further ado, let’s dive into the code:
# pbcopy via iTerm2 function pbcopy() { if which pbcopy >/dev/null 2>&1; then pbcopy "$@" else # Replace ^[ with the ASCII escape character local start="\e]1337;CopyToClipboard\a" local end="\e]1337;EndCopy\a" printf "${start}$(cat)${end}" fi }Code language: PHP (php)You might be wondering how this magic works. Let’s break it down step by step:
- The function first checks if the
pbcopycommand is available in your shell environment using thewhichcommand. If it is, it simply calls the built-inpbcopycommand with the provided arguments. - If the
pbcopycommand isn’t available, the function creates two local variables,startandend, containing special escape sequences that iTerm2 understands. - The
printfcommand is then used to concatenate thestartescape sequence, the contents of the input (read usingcat), and theendescape sequence. This formatted string is what gets sent to iTerm2.
When iTerm2 receives this specially formatted string, it recognizes the escape sequences and takes care of copying the text between them to the system clipboard.
And that’s it! With this function in your shell environment, you can copy text to your clipboard using
pbcopywithout any hassle, whether the command is natively available or not. To use this function, simply add it to your shell startup file (like.bashrcor.zshrc), and you’re good to go.In a practical scenario, let’s say you’re working on a project and you want to share the contents of a log file with a colleague. Instead of manually opening the log file, selecting its content, and copying it to your clipboard, you can use this shell script to streamline the process. With the
pbcopyfunction added to your shell startup file, simply open your terminal and run the commandcat logfile.txt | pbcopy. This command will read the content of thelogfile.txtfile and pipe it directly to thepbcopyfunction, which in turn places the content into your clipboard. Now you can easily paste the log file content into an email, chat application, or any other medium you choose, all without leaving the comfort of your terminal. This not only saves time but also enhances your overall productivity when working with text-based data in a command-line environment.In conclusion, this shell function is a fantastic little tool to enhance your iTerm2 experience on macOS. I hope you find it as useful as I do. As always, if you have any questions or comments, feel free to drop them below. Happy coding!
Happy coding, indeed 🤖
- The function first checks if the
-

More Garbage Sysadmin: Reboot Linux Server on Kernel Panic
Just like restarting a server when the memory is low, I’ve had a recent problem with kernel panics on my Raspberry Pi, and I’ve found a terrible solution to fix it: Just reboot.
Because I’m lazy, I asked ChatGPT to write me up a startup script to do this for me, and here’s what I have now:
To set the
panictimeout value on Ubuntu Server 20.04 and later versions, you can create a systemd service unit file.Here are the steps to create a systemd service unit file:
- Open a terminal window on your Ubuntu Server.
- Create a new service unit file with the following command:
sudo nano /etc/systemd/system/panic-timeout.service
This will open a new file namedpanic-timeout.servicein the nano text editor with superuser privileges. - Add the following lines to the file:
[Unit] Description=Panic Timeout [Service] Type=oneshot ExecStart=/bin/bash -c "echo 60 > /proc/sys/kernel/panic" [Install] WantedBy=multi-user.targetCode language: JavaScript (javascript)This service unit file sets the panic timeout to 60 seconds.
- Save the file by pressing
Ctrl+O, then exit nano by pressingCtrl+X. - Reload the systemd daemon to recognize the new service unit file with the following command:
sudo systemctl daemon-reload - Enable the service unit file to run at boot time with the following command:
sudo systemctl enable panic-timeout.service - Reboot the server to test the service unit file. After the server reboots, the
panic-timeout.servicewill automatically run theechocommand and set the panic timeout to 60 seconds.
That’s it! With these steps, you can set the
panictimeout value on the latest versions of Ubuntu Server.Well there you have it! Don’t forget to follow along for more terrible ideas!


