r/bash • u/Heavy_Spite6441 • 1h ago
r/bash • u/da_Aresinger • 18h ago
Unset doesn't properly work in functions??
So this is super weird. When I run the following in a script (or manually in the terminal), it does exactly what you'd expect.
```Bash declare -A myassoc myassoc[key]="Hello World"
unset myassoc
declare -p myassoc
echo "This should be empty: ${myassoc[@]}"
OUTPUT:
./unsetLovalVar: line 9: declare: myassoc: not found
This should be empty:
``
As expected, myassoc is unset and therefore undeclared
however if I wrap that code in a function, the myassoc becomes unset but not undeclared. ```Bash foo () { declare -A myassoc myassoc[key]="Hello World"
unset myassoc
declare -p myassoc
echo "This should be empty: ${myassoc[@]}"
}
foo
OUTPUT:
declare -- myassoc
This should be empty:
```
This is very annoying since I use declare -p to test if variables exist regardless of their type and content.
I have tested this on two machines in Debian and Ubuntu on Bash 5.2.something with the same issue.
test is not a viable alternative, since it doesn't have the same behaviour for local myvar and declare myvar (vars without value return false, even if declared)
Does anyone have a workaround?
EDIT:
So I found this StackExchange topic which seems to conclude that this is simply not possible within the same scope.
Here is where it gets REALLY insane:
```Bash bar () { declare -A myassoc myassoc[key]="Hello World"
declare -p myassoc
echo "This should not be empty: ${myassoc[@]}"
foo () {
unset myassoc
echo "In foo:"
declare -p myassoc
echo "This should be empty: ${myassoc[@]}"
}
foo
echo "After foo:"
declare -p myassoc
echo "This should be empty: ${myassoc[@]}"
} bar ``` \please excuse the ridiculous indents, I havent gotten around to taming nvim yet))
THIS WORKS FOR ABSOLUTELY NO REASON:
Bash
OUTPUT:
declare -A myassoc=([key]="Hello World" )
This should not be empty: Hello World
In foo:
./unsetLocalVar: line 12: declare: myassoc: not found
This should be empty:
After foo:
./unsetLocalVar: line 17: declare: myassoc: not found
This should be empty:
The solution is to wrap the unset in a nested function.
r/bash • u/naffe1o2o • 2d ago
is there a way to improve a github release updater to pick the right asset name?
i made a script that actually works for most times to update brave browser, but for packages that change names its trivial. the idea is to place, update directory with a file with metadata that i can extract. here is the script :
updatePrograms() {
for dir in ~/Programs/*; do
#meta file structure
if [[ -f "$dir"/update/meta ]]; then
local metadata=$(cat "$dir"/update/meta)
local owner=$( echo "$metadata" | grep "Owner" | sed 's|Owner:||' )
local project=$(echo "$metadata" | grep "Project" | sed 's|Project:||')
local output=$(curl -fsS "https://api.github.com/repos/$owner/$project/releases/latest")
[[ -z "$output" ]] && { echo "no response for $project"; continue; }
local checkVersion=$(jq -r '.tag_name' <<< "$output")
echo "updatable Project : $project"
[[ "$checkVersion" = "null" ]] || [[ -z "$checkVersion" ]] && { echo "couldn't fetch version for $project"; continue; }
local currentVersion=$(echo "$metadata" | grep "Version" | sed 's|Version:||')
[[ "$checkVersion" = "$currentVersion" ]] && { echo "$project is up to date : $checkVersion"; continue; }
while true; do
read -p "$project is on $currentVersion, latest is $checkVersion; do you want to request the latest Version? [y/n]: " response
case "$response" in
[Yy])
echo "Updating.."
local name
name=$(echo "$metadata" | grep "AssetName" | sed 's|AssetName:||')
local url=$(jq -r --arg name "$name" '.assets[] | select(.name == $name) | .browser_download_url' <<< "$output")
if [[ -z "$url" ]] || [[ "$url" = "null" ]]; then
echo "couldn't find asset matching $name"
echo "you may need to change asset name version to $checkVersion"
break
fi
if curl -fL -o "$dir/update/$name" "$url" ; then
sed -i "s|Version:.*|Version:$checkVersion|" "$dir"/update/meta
else
echo "Downloading falied"
break
fi
break;;
[Nn])
echo "not Updating"
break;;
*)
echo "choose y or n";;
esac
done
fi
done
}
r/bash • u/bderenzo • 2d ago
Tinybackup: a tiny Kopia backup wrapper
For all those who want simple backups of their machines, I have created a tiny bash wrapper around Kopia: https://github.com/bderenzo/tinybackup
From a single minimal config file, it handles repository setup, retention, ignore rules and pre/post backup hooks, etc.
It can back up to a local disk, SFTP, S3 or any rclone remote, and lets you browse snapshots through CLI, a mount point or the Kopia web UI.
r/bash • u/PrestigiousZombie531 • 4d ago
critique Many things could go wrong with this script and leave a running process, how would you recommend fixing this?
```bash
!/usr/bin/env bash
function main() {
# Phase 1: Setup and Run Dev Server
printf "%s\n" "Starting development server..."
cd "$HOME/Desktop/my-project-1" || exit 1
# Run the dev server in the background
rm -rf coverage dist && npm run lint && npm run coverage && npm run test && npx tsc || exit 1
npx tsx watch --env-file=.env.development src/index.ts > "/tmp/server.log" 2>&1 &
DEV_PID=$! # Store the Process ID of the 'dev' server
# Monitor for "server is listening"
printf "%s\n" "Waiting for server startup message..."
while ! grep -q "Server is now listening" "/tmp/server.log"; do
printf "%s\n" "Server not yet ready. Still waiting..."
sleep 1 # Wait 5 seconds before checking the log again
done
printf "%s\n" "SUCCESS: Server is listening. Proceeding to Phase 2."
# Phase 2: API Call and Wait
printf "%s\n" "Executing API call..."
rm -rf /tmp/test_app && curl -X POST http://localhost:3003/job/node/express/basic/generator
# Monitor for "downloading..." message in logs...
printf "%s\n" "Waiting for download completion..."
# Wait for the download confirmation
while ! grep -q "downloading docker container files completed successfully" "/tmp/server.log"; do
printf "%s\n" "Waiting for download confirmation..."
sleep 1
done
# Phase 3: Cleanup
printf "%s\n" "Shutting down services..."
kill ${DEV_PID} # Use the stored PID to gracefully stop the server process
printf "%s\n" "Moving into generated code..."
cd "/tmp/test_app" && npm install && code "/tmp/test_app" && cd "$HOME" || exit 1
}
main "$@"
```
r/bash • u/Business-Quiet-5805 • 6d ago
Can AI actually write production-ready scripts end-to-end without human intervention?
Script for making .sh files executable
I made this bash script for quickly doing chmod +x on sh files, uses zenity. Tell me if its any good or not
#!/bin/bash
zenity --info --title=".sh-executabler" --text="Welcome to the .sh-executabler" --ok-label="Next"
status=$?
if [ $status -ne 0 ]; then
zenity --info --title=".sh-executabler" --text="Wizard cancelled."
exit 0
fi
# Select which .sh file to chmod +x
target_dir=$(zenity --file-selection --title=".sh-executabler" --file-filter="Shell scripts | *.sh")
status=$?
if [ $status -ne 0 ]; then
zenity --info --title=".sh-executabler" --text="No file chosen."
exit 0
fi
# Check if file doesnt exist. Its kinda redundant since im already using file selection which you cant select files that dont exist but i keep it here anyways
if [ -f "$target_dir" ]; then
true
else
zenity --error --title=".sh-executabler" --text="The file doesnt exist."
exit 0
fi
chmod +x "$target_dir"
zenity --info --title=".sh-executabler" --text="The file has successfully been made executable." --ok-label="Finish"
exit 0
r/bash • u/Awkward_Process_9627 • 8d ago
fm: a single-file bash file manager — fzf picker, gallery grid, trash undo
I wrote a ~1500-line bash file manager. fzf for the picker, eza for
listing, Kitty's graphics protocol for inline image/video/PDF previews.
Everything else is stock coreutils.

Repo: https://github.com/eHab-codeX/fm
Why post it here
r/commandline got the "here's a tool" version last week. The bash bits are probably more interesting to this crowd, so here they are.
The bash-specific parts
Double-click detection without EPOCHREALTIME getting in the way.
EPOCHREALTIME returns a locale-formatted decimal — . in en_US,
, in de_DE. Strip the separator before comparing:
now=${EPOCHREALTIME/[.,]/}
(( idx == last_idx && now - last_t < 400000 )) # 400ms
A hand-rolled CSI/SS3 parser for the gallery grid. The full-screen grid needs arrow keys, PgUp/PgDn, Home/End, and SGR mouse events. Rather than depend on a helper, it reads bytes into a buffer and dispatches on the final byte:
case "$final" in
A) KEY=up ;; B) KEY=down ;;
C) KEY=right ;; D) KEY=left ;;
'~') case "${seq%%;*}" in
5) KEY=pgup ;; 6) KEY=pgdn ;;
esac ;;
esac
The tricky part is not swallowing stray bytes: an unknown CSI sequence
(\e[1;5H for Ctrl+Home, for example) has to be fully consumed or it
leaks into the next read as literal characters.
**stty -g around the alternate screen.** The grid switches to cbreak
mode (stty -icanon -echo min 1 time 0). Saving the previous state and
restoring it in the EXIT trap is what keeps Ctrl-C from leaving the
terminal in a broken state:
STTY_SAVED=$(stty -g </dev/tty) || STTY_SAVED=""
trap '[[ -n "$STTY_SAVED" ]] && stty "$STTY_SAVED" </dev/tty' EXIT
Two-phase rename so swaps work. A naive mv old new loop breaks
a→b, b→a. First move every source to a temp name, then move temps to
final names:
for k in "${!ro[@]}"; do
mv -T -- "${ro[k]}" "${ro[k]}.fm-tmp.$$"
done
for k in "${!ro[@]}"; do
mv -T -- "${ro[k]}.fm-tmp.$$" "${rn[k]}"
done
Parallel thumbnails by self-invocation. The thumbnail builder is
the same script with --thumb-any:
printf '%s\0' "${slice[@]}" | \
xargs -0 -r -n1 -P"$(nproc)" bash "$SELF" --thumb-any
Bash dispatches; ImageMagick/ffmpeg/pdftoppm do the work.
GNU vs BSD head. head -z is GNU-only. A contributor on macOS
sent a PR that falls back to ghead, then to a pure-bash reader:
while ((count < $1)) && IFS= read -r -d '' record; do
printf '%s\0' "$record"
((++count))
done
What I'd want reviewed
- The escape parser in
_g_readkey— there are terminals that emit sequences it might mis-handle (Konsole? older tmux?). - The
_fm_listsort pipeline — it does one batchedstatand oneawkpass, thensort -V. I'm not 100% sure the tie-breaking behavior for identical timestamps is stable. - Anything in
_trash_and_record— I'm handling the Freedesktop.trashinfoformat'sPath=percent-encoding with a bash-only encoder, which is probably slower than it should be.
Requirements
Bash 5+ (uses EPOCHREALTIME and ${var,,}). Linux is the primary
target; macOS works with GNU coreutils installed (the _head0 helper
falls back to bash-only if ghead isn't present).
git clone https://github.com/eHab-codeX/fm.git ~/.local/share/fm
ln -sf ~/.local/share/fm/fm ~/.local/bin/fm
MIT.
r/bash • u/mpersico • 10d ago
tips and tricks TIL command_not_found_handle
You don’t want to know what hoops I’ve jumped through to implement FPATH in bash. Then today I innocently asked GPT how it would implement FPATH in bash and it introduced me to this hook.
Now have to go home now get rid of a lot of needless code. Oh well. Less code is the best code!
r/bash • u/MonkiWasTooked • 11d ago
help For some reason, when I run this script, it always plays the same song.
When I run ls ~/Music/ | shuf | tail -1 | read file && echo $file it gives a random output as expected, but every time I run this script it plays a file labeled "3S.mp3" exclusively, which just so happens to be the first one in the list when I run ls
Does anyone know what could be causing this?
r/bash • u/Linux_bash_user153 • 13d ago
Some Tips for String Handling
When I was new to Bash/Shell I really didn't understand how to select or remove different parts of strings, and only used examples from forums without understanding at all how they worked. But then I read the man page for Bash (go figure...) and finally understood. I'd like to share in hope that it will be helpful to anyone starting out with Bash scripting. The examples work in POSIX shell also.
Example: select everything after the last _ in the string
str='first_second_third_fourth'
```
Selecting everything after the last '_' is the same as removing everything
before the last '' and the '' itself.
So check 'Remove matching prefix pattern' in 'man bash'.
${str##_} will remove the longest match of the pattern '_' starting
from the beginning of string and leave us with just 'fourth'.
str='firstsecond_third_fourth' echo "${str##*}" ```
Example: pick out second from the string
str='first_second_third_fourth'
```
We need to remove everything before the first '_' so we'll use
'second=${str#*_}' to remove the shortest match from the beginning.
Variable 'second' now contains 'second_third_fourth' so we need to
get rid of everything after the first '_'.
See 'Remove matching suffix pattern' in the manual.
We want to remove the longest match of pattern '_*' starting
from the end of the string. So '${second%%_*}' is what we need.
All in all:
str='firstsecond_third_fourth' second="${str#*}" second="${second%%_*}" echo "$second" ```
Example: find the file extension (with the dot) of /home/user/Pictures/test.webp
```
The extension is the thing after the last '.'. So use '${f##*.}'
This will delete the '.' also, so let's add that back in
f='/home/user/Pictures/test.webp' ext=".${f##*.}" echo "$ext" ```
Example: convert a png to jpg ``` pngfile='original.png'
We need to replace the .png with a .jpg. I.e. delete .png and add .jpg
jpgfile="${pngfile%.png}.jpg" echo convert "$pngfile" "$jpgfile"
Notice that if pngfile doesn't end in .png, jpgfile will at least not
be the same file, so pngfile will not be overwritten.
```
Example: Remember to escape characters that are special in pattern matching ```
Remove the first sentence in this string
str='Hello? Anybody there?'
This will not work since '?' is a pattern matching exactly one character,
This will only remove the beginning 'H' ('*' matches nothing in this case)
echo "${str#*?}"
The solution is to make the '?' literal by escaping it or quoting it
echo "${str#\?}" echo "${str#'?'}"
The same goes for '*', '[' and ']'. And probably a few more as well,
especially if extglob is turned on
```
r/bash • u/Dismal-Divide3337 • 13d ago
Ever wanted to attach to a background process and monitor stdout?
We create applications and output debugging and status information to stdout. Then when these are run in the background that all ends up in the bit bucket.
But when you need to debug something and you don't want to disturb the process what do you do? I could kill it and then run it in the foreground. But that disturbs things too much.
Okay, so what if `stdout` gets logged to a `stdout.log file`? Since I own the OS that was a light-weight change. It works. And I can use `CAT -M stdout.log` which is my equivalent to `tail -f stdout.log`.
Okay, but then I have a unit that is running 5 applications. One of which the author is always very verbose in the foreground (sounds like a personal/personnel problem). That overwhelms the log. Those age to BAK and monitoring the log sucks.
So here is my solution. Your thoughts?
\--------------
Here is what I have implemented for this. By default background processes will **not** log `stdout` output unless if already has the log file. So there would be no net change from JANOS v2.5.3.
If you want to log background process output generally, you can use the following command:
touch stdout.log
All `stdout` from all background processes will then be captured in this file. The `stdout.log` file must first exist before it will collect `stdout` stream data.
This will age to `stdout.log.bak` as any other log file would.
You can stop logging by removing (or renaming) this file.
When there are multiple applications running. You may optionally create an application specific `stdout` log file also using the `TOUCH` command. This file must be named using the specific application name. For example:
touch stdout-tasker.log
The application name is everything after the last '/' in the process name reported by `PS`. If that includes `.jar` then the stdout file name should also. I tend to not include the `.jar` in `Run` keys as that will be carried to the process name. It is unnecessary.
This will route output from the application to that specific file which will age accordingly. Meanwhile, if it exists, other applications would still log to `stdout.log`.
So this lets you enable `stdout` capture for a specific process and then monitor it.
r/bash • u/jjangg96 • 15d ago
tips and tricks Editing a bash script while it's running gives you errors from lines that don't exist
spent a while chasing a bug that wasn't there, so here's the tell.
i had a polling script running in the background and edited it while it was going. started getting s: command not found and pr: unbound variable. nothing in the file looked like that. i went and "fixed" a couple of things that were never broken.
bash doesn't read the whole file up front, it reads as it goes by byte offset. edit the file and it resumes at whatever byte it was at, which is now the middle of some other line. s and pr were fragments of words that used to be somewhere else.
quick way to rule it in or out: if the error says line 214 and the file only has 190 lines, it's this, not a real bug.
fixes: cp the script and run the copy, or put everything in a main() and end the file with main "$@"; exit on one line, so bash never reads past it.
edit: thanks to the comments, bash reads the file in blocks (up to 8192 bytes), not literally byte by byte, and calling main alone isn't enough, the exit has to be on the same line.
r/bash • u/Muted-Shame9237 • 16d ago
File for loop
hi! it’s my first time using bash can someone help me with this and explain what’s missing
#!/bin/bash
for i in “/data/folder”/*; do
/folder/munge_sumstats.py \
- -sumstats “$i” \
- -chunksize 500000 \
- -snp rsID \
etc..
- -out another/folder/ (i dont know what to puT here)
done
Thank you so much !
critique [First Time Script] Is this script good or should I not touch bash ever again?
Hello! I'm new to Linux and Bash. This script is supposed to be a part of a GitHub workflow for publishing my Bevy game to GitHub pages.
It's supposed to do this: (Each list entry corresponds to a STAGE in the script.)
- Identifies what distribution is being used, currently supports only Arch, CachyOS and Ubuntu (GitHub actions use Ubuntu). Updates the system and installs dependencies needed for compilation.
- Updates the Rust installation using
rustup updateinstall the nightly tool-chain, set it as default and install thewasm32-unknown-unknowntarget. - Checks if
wasm-bindgen-cliisn't already installed, if it isn't it installs it. - Compiles the project in release mode with the above mentioned target.
- Generates the
dist/folder by first creatingdist/anddist/assetsthen copies contents of theweb/folder, which now contains onlyindex.html, todist/also copies contents ofassets/todist/assets/and finally uses wasm-bindgen to generate bindings for the project.
Here's the script:
#!/bin/bash
# Variables.
. /etc/os-release # Bring needed variables to scope.
PROJECT_NAME="comfort-3d"; # Name.
HUMAN_PROJECT_NAME="Comfort 3D"; # Human readable name.
WASM_BINDGEN_BIN="wasm-bindgen"; # Name of wasm-bindgen-cli executable.
DIST_FOLDER=dist/; # Distribution folder.
DIST_ASSETS_FOLDER=dist/assets/; # Distrubution assets folder.
WEB_FOLDER=web/.; # Web folder.
ASSETS_FOLDER=assets/.; # Assets folder.
RUST_TOOLCHAIN="nightly-x86_64-unknown-linux-gnu"; # Nightly Rust toolchain.
RUST_TARGET="wasm32-unknown-unknown"; # Rust wasm target.
# Dependencies for Arch and Arch derived distros.
DEPENDENCIES_ARCH="libx11 pkgconf alsa-lib libxcursor libxrandr libxi gcc git pipewire-alsa pulseaudio-alsa vulkan-intel vulkan-radeon mold";
# Dependencies for Ubuntu.
DEPENDENCIES_UBUNTU="g++ mold clang git pkg-config libx11-dev libasound2-dev libudev-dev libxkbcommon-x11-0 vulkan-radeon vulkan-intel mesa-vulkan-drivers";
# File needed for wasm-bindgen.
WASM_FILE=target/wasm32-unknown-unknown/release/${PROJECT_NAME}.wasm;
# Color and format variables.
BOLD="\e[1m";
RESET_FORMAT="\e[0m";
RED="\e[91m";
GREEN="\e[92m";
CYAN="\e[96m";
# STAGE 1: Update system, Install needed dependencies, Install toolchain and target.
case $ID in # Variable comes from ". /etc/os-release"!
ubuntu)
echo -e "${CYAN}This is Ubuntu! Updating system...${RESET_FORMAT}";
# Update system.
if sudo apt-update && sudo apt-upgrade 2>&1; then
echo -e "${GREEN}Successfully updated system!${RESET_FORMAT}";
else
echo -e "${BOLD}${RED}There was an error updating the system! Aborting...${RESET_FORMAT}";
exit 1;
fi
# Install dependencies, make sure gcc and mold is installed.
echo -e "${CYAN}Installing dependencies...${RESET_FORMAT}";
if sudo apt-get install $DEPENDENCIES_UBUNTU 2>&1; then
echo -e "${GREEN}Successfully installed dependencies!${RESET_FORMAT}";
else
echo -e "${BOLD}${RED}There was an error installing dependencies! Aborting...${RESET_FORMAT}";
exit 1;
fi
;;
arch)
echo -e "${CYAN}This is Arch! Updating system...${RESET_FORMAT}";
# Update system.
if sudo pacman -Syu 2>&1; then
echo -e "${GREEN}Successfully updated system!${RESET_FORMAT}";
else
echo -e "${BOLD}${RED}There was an error updating the system! Aborting...${RESET_FORMAT}";
exit 1;
fi
# Install dependencies, make sure gcc and mold is installed.
echo -e "${CYAN}Installing dependencies...${RESET_FORMAT}";
if sudo pacman -S $DEPENDENCIES_ARCH 2>&1; then
echo -e "${GREEN}Successfully installed dependencies!${RESET_FORMAT}";
else
echo -e "${BOLD}${RED}There was an error installing dependencies! Aborting...${RESET_FORMAT}";
exit 1;
fi
;;
cachyos)
echo -e "${CYAN}This is CachyOS! Updating system...${RESET_FORMAT}";
# Update system.
if sudo pacman -Syu 2>&1; then
echo -e "${GREEN}Successfully updated system!${RESET_FORMAT}";
else
echo -e "${BOLD}${RED}There was an error updating the system! Aborting...${RESET_FORMAT}";
exit 1;
fi
# Install dependencies, make sure gcc and mold is installed.
echo -e "${CYAN}Installing dependencies...${RESET_FORMAT}";
if sudo pacman -S $DEPENDENCIES_ARCH 2>&1; then
echo -e "${GREEN}Successfully installed dependencies!${RESET_FORMAT}";
else
echo -e "${BOLD}${RED}There was an error installing dependencies! Aborting...${RESET_FORMAT}";
exit 1;
fi
;;
*)
echo -e "${BOLD}${RED}This is an unknown distribution! Aborting...${RESET_FORMAT}";
exit 1;
;;
esac
# STAGE 2: Update Rust, Install toolchain, Set toolchain as default, Install target wasm32-unknown-unknown.
echo -e "${CYAN}Updating Rust with rustup...${RESET_FORMAT}";
if rustup update 2>&1; then
echo -e "${GREEN}Successfully updated Rust using rustup!${RESET_FORMAT}";
echo -e "${CYAN}Installing Rust toolchain...${RESET_FORMAT}";
if rustup toolchain install "$RUST_TOOLCHAIN" 2>&1; then
echo -e "${GREEN}Sucessfully installed ${RUST_TOOLCHAIN}!${RESET_FORMAT}";
echo -e "${CYAN}Setting $RUST_TOOLCHAIN as default...${RESET_FORMAT}";
if rustup default "$RUST_TOOLCHAIN" 2>&1; then
echo -e "${GREEN}Sucessfully set $RUST_TOOLCHAIN as default!${RESET_FORMAT}";
echo -e "${CYAN}Installing target ${RUST_TARGET}${RESET_FORMAT}";
if rustup target add $RUST_TARGET >/dev/null 2>&1; then
echo -e "${GREEN}Sucessfully installed target ${RUST_TARGET}${RESET_FORMAT}";
else
echo -e "${RED}${BOLD}There was an error installing target ${RUST_TARGET}! Aborting...${RESET_FORMAT}";
exit 1;
fi
else
echo -e "${BOLD}${RED}There was an error setting $RUST_TOOLCHAIN as default! Aborting...${RESET_FORMAT}";
exit 1;
fi
else
echo -e "${BOLD}${RED}There was an error installing ${RUST_TOOLCHAIN}! Aborting...${RESET_FORMAT}";
exit 1;
fi
else
echo -e "${BOLD}${RED}There was an error updating Rust using rustup! Aborting...${RESET_FORMAT}";
exit 1;
fi
# STAGE 3: Check for wasm-bindgen-cli, if missing install it.
if command -v "$WASM_BINDGEN_BIN" >/dev/null 2>&1; then
echo -e "${GREEN}Binary $WASM_BINDGEN_BIN is intalled!${RESET_FORMAT}";
else
echo -e "${BOLD}${RED}Binary $WASM_BINDGEN_BIN could not be found! Installing...${RESET_FORMAT}"
if ! cargo install wasm-bindgen-cli >/dev/null 2>&1; then
echo -e "${BOLD}${RED}There was an error installing wasm-bindgen-cli! Aborting...${RESET_FORMAT}";
exit 1;
fi
fi
# STAGE 4: Compile project with target wasm32-unknown-unknown.
echo -e "${CYAN}Compiling project with target ${RUST_TARGET}${RESET_FORMAT}";
if cargo build --release --target $RUST_TARGET >/dev/null 2>&1; then
echo -e "${GREEN}Sucessfully compiled project with target ${RUST_TARGET}${RESET_FORMAT}";
else
echo -e "${RED}${BOLD}There was an error compiling project with target ${RUST_TARGET}! Aborting...${RESET_FORMAT}";
exit 1;
fi
# STAGE 5: Assemble dist folder.
echo -e "${CYAN}Generating $DIST_FOLDER folder...${RESET_FORMAT}";
if mkdir -p $DIST_FOLDER >/dev/null 2>&1; then # Create dist/.
if mkdir -p $DIST_ASSETS_FOLDER >/dev/null 2>&1; then # Create dist/assets/.
if cp -a $WEB_FOLDER $DIST_FOLDER >/dev/null 2>&1; then # Copy web/ contents to dist/.
if cp -a $ASSETS_FOLDER $DIST_ASSETS_FOLDER >/dev/null 2>&1; then # Copy assets/ contents to dist/assets.
if wasm-bindgen --out-name $PROJECT_NAME --out-dir $DIST_FOLDER --target web $WASM_FILE 2>&1; then # Use wasm-bindgen to generate files.
echo -e "${GREEN}Sucessfully generated ${DIST_FOLDER} folder!${RESET_FORMAT}";
else
echo -e "${RED}${BOLD}There was an error generating ${DIST_FOLDER}! Aborting...${RESET_FORMAT}";
exit 1;
fi
else
echo -e "${RED}${BOLD}There was an error copying files from $ASSETS_FOLDER to ${DIST_ASSETS_FOLDER}! Aborting...${RESET_FORMAT}";
exit 1;
fi
else
echo -e "${RED}${BOLD}There was an error copying files from $WEB_FOLDER to ${DIST_FOLDER}! Aborting...${RESET_FORMAT}";
exit 1;
fi
else
echo -e "${RED}${BOLD}There was an error creating ${$DIST_ASSETS_FOLDER}! Aborting...${RESET_FORMAT}";
exit 1;
fi
else
echo -e "${RED}${BOLD}There was an error creating ${DIST_FOLDER}! Aborting...${RESET_FORMAT}";
exit 1;
fi
The script works as expected but I don't know if there aren't some weird edge cases and such.
r/bash • u/UnholyScholar • 19d ago
backup script logic
I've written this backup script for use copying my files from point A to point B. The goal is a full snapshot of files. For this first iteration, I'm ok with hard coding file paths.
I've removed everything except logic for rsync, diff, and logging. Are there any logic issues?
# backup script that performs full backups and verifies the result.
#!/bin/bash
set -euo pipefail
DATE_STAMP=$(date +%Y-%m-%d_%H-%M-%S)
BACKUP_LOCATION="$HOME/backup-location-path/$DATE_STAMP"
LOG_LOCATION="$HOME/log-location-path/"
LOGFILE_NAME="backup_${DATE_STAMP}.log"
SOURCE_DIRS=(
"$HOME/source1"
"$HOME/DOES-NOT-EXIST" # DNE for error handling
"$HOME/source2"
)
# check if source matches destination
diff_source_and_destination() {
local base
base=$(basename "$SRC")
diff -r "$SRC" "$BACKUP_LOCATION"/"$base"
}
# Check for the existence of $SRC and print an alert if it wasn't found.
check_source_exists() {
if [ -e "$SRC" ]; then
echo "[SOURCE = $SRC] found. Copying..."
return 0
else
echo "[SOURCE = $SRC] WAS NOT FOUND!" | tee -a "$LOG_LOCATION/$LOGFILE_NAME"
return 1
fi
}
copy_data_and_log() {
{
rsync -a --stats "$SRC/" "${DESTINATION}/"
} | tee -a "$LOG_LOCATION"/"$LOGFILE_NAME"
}
mkdir -p "$BACKUP_LOCATION"
mkdir -p "$LOG_LOCATION"
echo "Copying files to [BACKUP LOCATION =${BACKUP_LOCATION}]"
for SRC in "${SOURCE_DIRS[@]}"; do
# Print and log a warning if SRC wasn't found
check_source_exists || continue
DESTINATION="$BACKUP_LOCATION/$(basename "$SRC")"
copy_data_and_log
echo "rsync completed for $SRC."
diff_source_and_destination
done
echo "Finished copying operations to [BACKUP LOCATION = ${BACKUP_LOCATION}]"
r/bash • u/Brave-Historian-946 • 19d ago
Lexis: A zero-dependency i18n lookup engine written in pure Bash 4+
Hello everyone!
I built a pure Bash 4+ runtime for Lexis, a minimalist i18n engine designed under the Unix philosophy for CLI tools and shell scripts.
It handles translation lookup entirely in native shell logic using an eager+lazy model: it loads and validates .lex key::value files into associative arrays, and lazily parses escape sequences and printf interpolations on first access with zero subshells or external binaries (sed/awk).
Real-World CLI Example (AuraConfig i18n module)
Here is a practical demonstration of localizing a CLI tool like auraconfig:

1. Translation File (lang/en.lex):
cli.config_loaded::Configuration loaded successfully from %s.
cli.option_set::Option '%s' set to '%s'.
cli.error_not_found::Error: Configuration key '%s' does not exist.
2. Shell Script (i18n.sh):
#!/usr/bin/env bash
source "runtimes/bash/lexis.sh"
# Loads 'fr.lex' with fallback to 'en.lex' (explicit here, though
# "en" is also the default if the third argument is omitted)
if ! lexis_load "lang" "fr" "en"; then
echo "Error: Failed to load translation files" >&2
exit 1
fi
CONFIG_FILE="$HOME/.config/aura.conf"
# Defensive fallback message if the key is missing from the .lex file
if [[ ! -f "$CONFIG_FILE" ]]; then
lexis_get_or_default "cli.error_not_found" "Error: Configuration file '%s' not found." "$CONFIG_FILE" >&2
exit 1
fi
# Standard lookup with printf interpolation
lexis_get "cli.config_loaded" "$CONFIG_FILE"
Key Technical Details
- Zero External Dependencies: Pure Bash logic relying strictly on associative arrays and parameter expansion.
- Eager Loading + Lazy Caching: Validates file structure on lexis_load(), then caches formatted printf results on demand.
- Two-tier fallback: requested locale + a fallback locale (defaults to
"en", but can be set explicitly, e.g.lexis_load "lang" "fr" "pt"), plus fallback strings vialexis_get_or_default. - Clean Error Handling: Returns proper exit codes (0/1) and routes errors to stderr.
Bash Runtime Source Code:
https://github.com/edgarmasague/lexis/tree/main/runtimes/bash
Requirements: Bash 4.0+ (associative array support required).
Feedback and shell scripting critiques are welcome!
r/bash • u/TheTurkPegger • 21d ago
help What to research to do this?
I've written a basic script that makes connecting to my college's WIFI semi automatic. I would like to turn it into a bigger project by adding console prompts that collect and store data, and pull the data out of a file when needed in the future. Even though I have background with programming, I lack the skills to do it because I'm a English Language Teaching student and do the computer stuff as a hobby or something (I also help other open source projects for the love of the game). What am I supposed to research to get the necessary information I need?
The script in question works without any problems and its whole purpose is to open/run necessary windows/applications or scripts to make it easier for me to login to my school's captive portal, login page etc. The script is triggered by a keyboard shortcut. It uses nmcli to do its job.
r/bash • u/PrestigiousZombie531 • 22d ago
help 1 big question I always has was whether your functions should accept arguments or flags? Am I overthinking this?
- Take a look at this run_aws_ssm_get_parameter function below
- To call it I could do something like run_aws_ssm_get_parameter "false" "" "some_param" and it ll run it without a vault user
- Now obviously it kinda sucks that I am passing an empty string to indicate that we dont have an AWS vault user and I have to also memorize the position of the arguments
- One other way would be this? run_aws_ssm_get_parameter --debug=false --parameter_name=some_param? Is this a good idea or a bad idea?
- Mind you all the code written below is written by me manually, no AI no nothing
```bash function run_aws_ssm_get_parameter() { validate_command_available "${FUNCNAME[0]}" "aws" || return 1
validate_arg_min_count "${FUNCNAME[0]}" 3 "$#" "Usage: ${FUNCNAME[0]} <debug> <aws_vault_user> <parameter_name> [additional aws ssm flags]" || return 1
local -r debug="${1:-false}"
local -r aws_vault_user=$2
local -r parameter_name="$3"
shift 3
validate_arg_not_empty "${FUNCNAME[0]}" "debug" "${debug}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "parameter_name" "${parameter_name}" || return 1
validate_arg_type_boolean "${FUNCNAME[0]}" "debug" "${debug}" || return 1
local -a flags=(
"get-parameter"
"--name=${parameter_name}"
)
flags+=("$@")
[[ "${debug}" = true ]] && log_info "Fetching AWS SSM parameter with parameter_name:${parameter_name}..."
if [[ ! -z "${aws_vault_user}" ]]; then
if result="$(aws-vault exec "${aws_vault_user}" -- aws ssm "${flags[@]}")"; then
[[ "${debug}" = true ]] && log_info "AWS SSM parameter with parameter_name:${parameter_name} fetched successfully"
printf "%s\n" "${result}"
else
log_error "AWS SSM parameter fetch with parameter_name:${parameter_name} failed"
return 1
fi
else
if result="$(aws ssm "${flags[@]}")"; then
[[ "${debug}" = true ]] && log_info "AWS SSM parameter with parameter_name:${parameter_name} fetched successfully"
printf "%s\n" "${result}"
else
log_error "AWS SSM parameter fetch with parameter_name:${parameter_name} failed"
return 1
fi
fi
}
function validate_command_available() { validate_arg_min_count "${FUNCNAME[0]}" 2 "$#" "Usage: ${FUNCNAME[0]} <function_name> <command_name>" || return 1
local -r function_name="$1"
local -r command_name="$2"
shift 2
validate_arg_not_empty "${FUNCNAME[0]}" "function_name" "${function_name}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "command_name" "${command_name}" || return 1
if ! command -v "${command_name}" &>/dev/null; then
log_error "${FUNCNAME[0]}: Function "%s" requires "%s" but it was not found on PATH" "${function_name}" "${command_name}"
return 1
fi
}
function validate_arg_min_count() { local -r function_name="$1"
local -r expected_count="$2"
local -r actual_count="$3"
local -r message="${4:-}"
shift 4
if [[ -z "${function_name}" ]]; then
log_error "${FUNCNAME[0]}: function_name:${function_name} is required"
return 1
fi
if [[ -z "${expected_count}" ]]; then
log_error "${FUNCNAME[0]}: expected_count:${expected_count} is required"
return 1
fi
if [[ -z "${actual_count}" ]]; then
log_error "${FUNCNAME[0]}: actual_count:${actual_count} is required"
return 1
fi
if [[ ! "${expected_count}" =~ ^[0-9]+$ ]]; then
log_error "${FUNCNAME[0]}: expected_count must be a non-negative integer, got: %s" "${expected_count}"
return 1
fi
if [[ ! "${actual_count}" =~ ^[0-9]+$ ]]; then
log_error "${FUNCNAME[0]}: actual_count must be a non-negative integer, got: %s" "${actual_count}"
return 1
fi
if [[ "${actual_count}" -lt "${expected_count}" ]]; then
if [[ -z "${message}" ]]; then
log_error "${FUNCNAME[0]}: Function "%s" expected %d argument(s) but got %d" "${function_name}" "${expected_count}" "${actual_count}"
else
log_error "${message}"
fi
return 1
fi
}
function validate_arg_not_empty() { local -r function_name="$1"
local -r arg_name="$2"
local -r arg_value="$3"
shift 3
if [[ -z "${function_name}" ]]; then
log_error "${FUNCNAME[0]}: function_name:${function_name} is required"
return 1
fi
if [[ -z "${arg_name}" ]]; then
log_error "${FUNCNAME[0]}: arg_name:${arg_name} is required"
return 1
fi
if [[ -z "${arg_value}" ]]; then
log_error "${FUNCNAME[0]}: Function "%s" received empty value for argument "%s"" "${function_name}" "${arg_name}"
return 1
fi
}
function validate_arg_type_boolean() { validate_arg_min_count "${FUNCNAME[0]}" 3 "$#" "Usage: ${FUNCNAME[0]} <function_name> <arg_name> <arg_value>" || return 1
local -r function_name="$1"
local -r arg_name="$2"
local -r arg_value="$3"
shift 3
validate_arg_not_empty "${FUNCNAME[0]}" "function_name" "${function_name}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "arg_name" "${arg_name}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "arg_value" "${arg_value}" || return 1
case "${arg_value}" in
true | false) ;;
*)
log_error "${FUNCNAME[0]}: Function '%s' expected 'true' or 'false' for argument '%s', got: %s" "${function_name}" "${arg_name}" "${arg_value}"
return 1
;;
esac
} ```
r/bash • u/Fit-Background8249 • 24d ago
Flag ambiguity
I am building a shell framework with a series of commands and subcommands for task-managing, habit-tracking...
Within those, there is a collection of flags that hold different meanings:
-dDuration, Date...-nName, Note...-lLink, Length...
Should I use different letters for different meanings even if they belong to different commands?
I want a real bash-user's opinion, not just the AI's advice.
I personally think that it isn't that big of a deal.
But let me know what y'all think.
Thanks!
r/bash • u/PrestigiousZombie531 • 24d ago
help Do you guys think there is a more concise way to write these functions?
```bash
https://linux.die.net/man/8/usermod
function install_docker() { validate_arg_min_count "${FUNCNAME[0]}" 3 "$#" "Usage: ${FUNCNAME[0]} <debug> <version> <user>" || return 1
local -r debug="${1:-false}"
local -r version="$2"
local -r user="$3"
shift 3
validate_arg_not_empty "${FUNCNAME[0]}" "debug" "${debug}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "version" "${version}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "user" "${user}" || return 1
validate_arg_type_boolean "${FUNCNAME[0]}" "debug" "${debug}" || return 1
if dnf install "docker-${version}" \
--assumeyes \
--quiet; then
[[ "${debug}" = "true" ]] && log_info "docker installation successful $(docker --version)"
else
log_error "docker installation failed"
return 1
fi
if usermod --append --groups docker "${user}"; then
[[ "${debug}" = "true" ]] && log_info "adding user:${user} to the docker group successful"
else
log_error "adding user:${user} to the docker group failed"
return 1
fi
if newgrp docker; then
[[ "${debug}" = "true" ]] && log_info "changes to docker group successful"
else
log_error "changes to docker group failed"
return 1
fi
if systemctl enable docker.service &&
systemctl start docker.service; then
[[ "${debug}" = "true" ]] && log_info "starting docker service successful"
else
log_error "starting docker service failed"
return 1
fi
}
Install docker-compose
Don't use chmod u+x here since it runs as root
https://github.com/amazonlinux/amazon-linux-2023/issues/1032#issuecomment-3674146092
function install_docker_compose() { validate_arg_min_count "${FUNCNAME[0]}" 2 "$#" "Usage: ${FUNCNAME[0]} <debug> <version>" || return 1
local -r debug="${1:-false}"
local -r url="$2"
shift 2
validate_arg_not_empty "${FUNCNAME[0]}" "debug" "${debug}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "url" "${url}" || return 1
validate_arg_type_boolean "${FUNCNAME[0]}" "debug" "${debug}" || return 1
if curl \
--location "${url}" \
--output "/usr/local/bin/docker-compose" \
--show-error \
--silent; then
[[ "${debug}" = "true" ]] && log_info "Download for docker-compose successful, version:$(docker-compose --version)"
else
log_error "Download for docker-compose failed"
return 1
fi
if chmod +x /usr/local/bin/docker-compose; then
[[ "${debug}" = "true" ]] && log_info "Permission grant to execute docker compose by ec2-user successful"
else
log_error "Permission grant to execute docker compose by ec2-user failed"
return 1
fi
}
function install_git() { validate_arg_min_count "${FUNCNAME[0]}" 2 "$#" "Usage: ${FUNCNAME[0]} <debug> <version>" || return 1
local -r debug="${1:-false}"
local -r version="$2"
shift 2
validate_arg_not_empty "${FUNCNAME[0]}" "debug" "${debug}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "version" "${version}" || return 1
validate_arg_type_boolean "${FUNCNAME[0]}" "debug" "${debug}" || return 1
if dnf install "git-${version}" \
--assumeyes \
--quiet; then
[[ "${debug}" = "true" ]] && log_info "git installation successful $(git --version)"
else
log_error "git installation failed"
return 1
fi
}
function install_jq() { validate_arg_min_count "${FUNCNAME[0]}" 2 "$#" "Usage: ${FUNCNAME[0]} <debug> <version>" || return 1
local -r debug="${1:-false}"
local -r version="$2"
shift 2
validate_arg_not_empty "${FUNCNAME[0]}" "debug" "${debug}" || return 1
validate_arg_not_empty "${FUNCNAME[0]}" "version" "${version}" || return 1
validate_arg_type_boolean "${FUNCNAME[0]}" "debug" "${debug}" || return 1
if dnf install "jq-${version}" \
--assumeyes \
--quiet; then
[[ "${debug}" = "true" ]] && log_info "jq installation successful $(jq --version)"
else
log_error "jq installation failed"
return 1
fi
}
```
- Some of them are doing literally the same thing like installing git and jq and yet others in the name of installing are doing slightly different stuff
- There is also a tonne of validation checks everywhere. Not sure if there is a better way to put that along with the debug stuff
r/bash • u/Sugar_Entire • 25d ago
I made a little bash-perl heredoc that intends to be a bit better than envsubst.
I had been working with envsubst for some months and was fed up by its limitation to parse multiple files at once. It required a for loop just to parse those files and thus, I have created some bash-perl heredoc for the workaround. It is about 2.5K lines. This is intended to replace Envsubst for my system. (Envsubst++ for me)
```sh mkdir -p templates cat > templates/greeting.dcol <<'EOF' $PATH:[::HOME::]/.config/demo/greeting.conf # Generated by hyir from [::_SOURCE_BASENAME::] -- do not edit by hand user = [::USER::] greeting = "Hello, [::NAME:-world::]!" EOFmkdir -p templates cat > templates/greeting.dcol <<'EOF' $PATH:[::HOME::]/.config/demo/greeting.conf # Generated by hyir from [::_SOURCE_BASENAME::] -- do not edit by hand user = [::USER::] greeting = "Hello, [::NAME:-world::]!" EOF
Render it. Placeholders are filled in from the environment.
NAME=Ada hyir.sh templates/
cat ~/.config/demo/greeting.conf
# GeneRender it. Placeholders are filled in from the environment.
NAME=Ada hyir.sh templates/
cat ~/.config/demo/greeting.conf
```
sh
# Generated by hyir from greeting.dcol -- do not edit by hand
user = alice
greeting = "Hello, Ada!"
rated by hyir from greeting.dcol -- do not edit by hand
user = alice
greeting = "Hello, Ada!"
- Run it again. Nothing changed, so nothing is rewritten. Add --allow-debug to see what hyir decided: ```sh NAME=Ada hyir.sh --allow-debug templates/
@[populate:write(false)]: Skipped changing /home/alice/.config/demo/greeting.conf <- templates/greeting.dcol @[diagnostic:telemetry(On)]: Rendered 1 templates using 1 workers in 0.0026 seconds (all succeeded). ```
Header
$PATH:<target>|$REQUIRE:{VAR1,VAR2}|$PRE:{hook}|$RUN:{hook}
The header is the first line of the file. When a value is wrapped in { ... } it may span several lines (braces are matched properly, including nested ones). Segments are separated by |. Only $PATH is required. Put $RUN last: it may itself contain | (for example a shell pipeline), so it absorbs everything after it.
If we want to write the template into a multiple file:
ts
$PATH:{[::HOME::]/TEMP, [::HOME::]/TEMP1}
The flag --proc [N|auto] renders in parallel worker processes. Output stays deterministic: templates that resolve to the same target are grouped onto one worker and processed in order.
Fallbacks is also part of the script:
```
$PATH:[::NAME:-default::]
[::PLACEHOLDER:-SOME_ANOTHER::]
```
It is an independent script that can be adopted into any environment where it requires config generations, do runtime-template polymorphism (I am not sure if it is accurate enough to say 'polymorphism') and use it for kubernets, etc. Feel free to check out.
r/bash • u/Odd_Inflation428 • 25d ago
EverythingX: a blazing fast file name search tool for macOS and Linux
r/bash • u/how_to_kubuntu • 26d ago
help using bash to make a random password?
hello, i heard that you can make a random password with bash? is that true? if so, how do you do that? thank you