r/bash • • 18h ago

Replacing dirname with only builtins

1 Upvotes

I am writing a bunch of scripts for which I want to only use builtins and realised that dirname is not a builtin, I replaced it thusly:

SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SRC_DIR2="$({ cd "${BASH_SOURCE[0]%/*}" 2>/dev/null || :; } && pwd)"

I think these two lines are functionally equivalent, considering that BASH_SOURCE[n] will never contain a trailing /.

Then again I am often stupid. Does anyone see any issues?


r/bash • • 1d ago

Improve your Bash testing with Bats

6 Upvotes

I started solopreneuring a year ago, and my projects have a fair amount of Bash. My annoyances with Bats: I couldn't mock things efficiently, the assertion libraries are scattered over three repos with different naming, and table-driven tests are clumsy. I've written these scripts over the years in my career and thought about open-sourcing them, but never did. I'm using these libraries in my own frameworks and it helps me test my bash scripts in a more unified way, so I've chosen to productionize them and finally putting it out there:

bats-mock mocks and spies on commands and Bash functions. You set behavior with glob/regex rules, run your code, then assert on call counts, exact argv, stdin and call order across commands. Spies call through to the real thing. jasonkarns/bats-mock uses stubs on PATH, so it can't mock a function without unsetting it, and has no spies or stdin checks.

https://github.com/stealth-scale/bats-mock

bats-expect replaces bats-support, bats-assert and bats-file with one library. bats-assert's functions keep their names, options and failure format, so switching is a one-line change. Naming is consistent, and it adds assertions for variables, arrays, namerefs, numbers and JSON.

https://github.com/stealth-scale/bats-expect

bats-matrix does table-driven tests: inputs, expected exit status and expected output in one table, and a failure tells you which row broke and the exact command it ran. Bats' own bats_test_function works too, but needs a loop and a separate test body.

https://github.com/stealth-scale/bats-matrix

bats-test is the image I run it all in: every combination of Bash 4.4/5.1/5.2/5.3 and Bats 1.7.0/1.14.0, GNU tools, jq and a patched kcov.

https://github.com/stealth-scale/bats-test

I've been an engineer for a long time and I care about quality, so my libraries have a 100% line coverage gate in CI. That's how I ended up in a kcov rabbit hole: on Bash 5.3 one suite came out at 0%. kcov's Bash support had three bugs: a single quote inside [[ ]] made it drop every later hit, it counted every line of a multi-line command while Bash reports one, and its helper killed any bash -c child running with set -u. With the fixes, bats-mock went from 659 lines at 88% to 613 at 100%. The patches are in bats-test. I haven't sent them upstream yet, and I know I should.

If you don't mind switching frameworks, bashunit and ShellSpec already have mocks and data-driven tests. These repos are for staying on Bats.

The linked repo is a small release CLI whose test suite uses all four:

git clone --recurse-submodules https://github.com/stealth-scale/bats-example.git && cd bats-example && make test

It uses Podman (make test RUNTIME=docker for Docker) and runs without network. You need Bash 4.4+, and since the mocks are functions, /usr/bin/curl goes straight past them.

Everything is MIT (the image ships kcov, which is GPL-2.0). For me these tools are a big time-saver.

I'd love to hear what's still annoying to test in your Bash so I can further improve what I've used for years.


r/bash • • 10h ago

submission Working on the Bash interpreter in JavaScript

0 Upvotes

I'm working on the Bash interpreter (Unix env) and making use of an old domain:

hacking.cafe.

The code is open source on the AGPL license available on GitHub.

I was thinking about making the bash interpreter part a library on a less restrictive license.

The README includes bash features that I've implemented so far.

I'm using ubash JavaScript parser.


r/bash • • 1d ago

Race condition with ` | tee -a >(col -b >> "$f")` and following function/command that also appends to $f

5 Upvotes

I'm writing to file $f using the snippet above, and the command after it also writes/appends to $f.

I'm using col because it's for fio --eta=always to display on terminal but not fill up the file with carriage returns or whatever commands that clutter the file and are intended control for the terminal for the purpose of live status updates.

Running it as in the title, the next command begins before col is done and their respective outputs mix a bit.

I believe the problem is that tee is being recognized as having finished/closed whereas >(col -b >> "$f") isn't being tracked and is still writing. I don't understand fully though I recognize, "The process... is run asynchronously."

I've found two solutions, but they don't quite feel right. Let me know if you can think of a better one or of which these two that you prefer.

{
fio --eta=always "${job}" | tee -a >(col -b >> "$f")
} &

wait $!

or

fio --eta=always "${job}" | tee /tmp/fio.log.tmp

cat /tmp/fio.log.tmp | col -b >> "$f"
rm /tmp/fio.log.tmp

The second one, I could probably add a mktemp and trap to both secure and delete the temp file.


r/bash • • 2d ago

help First time learning Bash: macOS vs Linux VM?

17 Upvotes

I'm learning Bash via Codecademy(maybe, idk if I'll pay for the subscription) and LinkedIn Learning. The specific course I'm starting says macOS is okay but using a Linux VM with something like Virtual Box is preferred since Linux is industry standard.

I haven't worked with VMs before but it is also something I want to learn about, but I don't know what the curve is like for setting something like this up or if it's worth doing as a beginner.

What do y'all recommend?


r/bash • • 4d ago

help What should i do now

15 Upvotes

Heyy i am new to Linux(currently using fedora silver blue) and exploring Linux, recently started exploring Bash for interest now i learn little bits of it like(basic commands , file permissions) from web3school , now what should i learn next ? From where(resource)


r/bash • • 4d ago

submission automatically write a shortcut to go to a specific directory

0 Upvotes

I personally have found that on linux systems, the biggest number of aliases are simply "alias <name> = 'cd <path>'", so it makes sense then to automate this. I wasn't interested making a github repository since this is very simple, and i would expect someone like me to perhaps make a slightly different version of this for whatever they do:

#!/bin/bash
#adds an alias to traverse a directory

ALIAS="$1"

echo "alias $ALIAS='cd \""$PWD"\"'" >> ~/.bash_aliases

echo "Written to .bash_aliases: alias $ALIAS='cd \""$PWD"\"'"

the thing that was fun about making this was figuring out the exact quoting to make it work, as some directories are inevitably going to have spaces etc.


r/bash • • 6d ago

set -e silently does nothing inside a function called from an if, and I only found out in production

28 Upvotes

I had a cleanup script that always started with the usual set -euo pipefail. One day it reported success while half the work hadn’t happened. Here’s a stripped-down version of what bit me:

#!/usr/bin/env bash
set -euo pipefail

cleanup_old() {
  rm "$1/old.log"        # fails if the file is missing
  echo "cleaned $1"
}

if cleanup_old /tmp/nope; then
  echo "all good"
fi

Output:

rm: cannot remove '/tmp/nope/old.log': No such file or directory
cleaned /tmp/nope
all good

The rm fails, but the function keeps going, and the echo at the end gives it a zero exit status. The bash manual says -e is ignored for any command in a context where the exit status is being tested (if, while, &&/|| lists, !). That also applies to everything inside a function called from such a context. set -e at the top of the file doesn’t help, and nothing warns you.

What I do now:

cleanup_old() {
  rm "$1/old.log" || return 1
  echo "cleaned $1"
}

The explicit || return is noisier, but it works regardless of how the function gets called. For anything where partial success is dangerous, I also stopped using functions as if conditions and run them as plain statements, checking $? or using || on the call site deliberately.

Related one that bit me the same week: local out=$(some_cmd) always returns the exit status of local, not some_cmd. Splitting it into local out; out=$(some_cmd) fixes it.

I’ve been reading a lot of generated and copy-pasted scripts lately, and nearly all of them treat set -euo pipefail as a safety net. After this, I think of it as a default that works in the simple cases and quietly stops working in exactly the places where functions get composed.

For those who write larger bash scripts: do you rely on set -e at all, or do you handle errors explicitly everywhere and keep -e only as a backstop?


r/bash • • 7d ago

submission Auditing every user’s authorized_keys in a Bash loop with jc + jq

24 Upvotes

I maintain jc, which converts command output and common file types to JSON so you can query them with jq instead of grep/awk/cut. A couple of newer parsers turned out to be handy in Bash scripts, so I wrote up some examples.

Why not just awk?

authorized_keys looks like a whitespace-delimited file, but it isn't. Lines can start with an optional, comma-separated options field, and option values can be quoted strings with spaces in them. So the field positions shift from line to line. Here's a sample file:

ssh-ed25519 AAAAC3Nza... kelly@laptop
command="/usr/bin/rsync --server --sender -logDtpre.iLsfxC . /srv/backup",no-port-forwarding,no-pty ssh-rsa AAAAB3Nza... backup@nas
from="10.0.8.0/24",no-agent-forwarding ecdsa-sha2-nistp256 AAAAE2Vj... ci-runner@build01
ssh-ed25519 AAAAC3Nza...

The obvious awk one-liner to print the key type and comment:

$ awk '{print $1, $3}' ~/.ssh/authorized_keys
ssh-ed25519 kelly@laptop
command="/usr/bin/rsync --sender
from="10.0.8.0/24",no-agent-forwarding AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz...
ssh-ed25519

Only the first line is right. Looking for RSA keys with awk '$1=="ssh-rsa"' returns nothing, because the one RSA key has options in front of it.

grep has the opposite problem: it matches text anywhere on the line. grep -v 'command=' to find unrestricted keys also drops a key whose comment says "no command= needed". And grep ssh-rsa matches a restricted ed25519 key whose forced command happens to contain the string ssh-rsa.

With jc --authorized-keys, every line comes out with the same fields, so there's no guessing:

$ jc --authorized-keys < ~/.ssh/authorized_keys | jq -r '.[] | "\(.type) \(.comment // "(no comment)")"'
ssh-ed25519 kelly@laptop
ssh-rsa backup@nas
ecdsa-sha2-nistp256 ci-runner@build01
ssh-ed25519 (no comment)

options is always an array, quoted values like command="echo ssh-rsa disabled; exit" stay intact, and multi-word comments are kept whole.

Tag every key with its owner, then query the whole box

A loop adds a user field to each key and emits JSON Lines:

for f in /home/*/.ssh/authorized_keys; do
    u=$(echo "$f" | cut -d/ -f3)
    jc --authorized-keys < "$f" | jq -c --arg u "$u" '.[] | . + {user: $u}'
done > keys.jsonl

Now each question is just a different jq filter. Which keys have no from= or command= restriction?

jq -r 'select(any(.options[]; startswith("from=") or startswith("command=")) | not)
       | "\(.user)  \(.type)  \(.comment // "(no comment)")"' keys.jsonl

Because this checks the options array instead of the whole line, comments and key data can't cause false matches.

Is the same key authorized for more than one user?

jq -s -r 'group_by(.key)[] | select(length > 1)
          | "\(.[0].comment // "(no comment)"): \(map(.user) | join(", "))"' keys.jsonl

Which legacy RSA or DSA keys are still around?

jq -r 'select(.type == "ssh-rsa" or .type == "ssh-dss") | "\(.user)  \(.comment)"' keys.jsonl

(Run the loop with sudo if you need to read other users' files.)

Bonus: a change-freeze guard from a shared calendar

.ics files are worse for line tools: long lines get folded onto the next line, and parameters are jammed into property names (DTSTART;TZID=America/Los_Angeles:20261019T090000). The --ics parser unfolds all of that and normalizes timestamps to ISO 8601. That means jq's fromdateiso8601 and now can check whether you're inside a freeze window, and jq -e turns the result into an exit code:

if jc --ics < freeze.ics | jq -e '
    [.[] | select(.categories[]? == "Freeze")
         | select((.dtstart | fromdateiso8601) <= now and now < (.dtend | fromdateiso8601))]
    | length > 0' > /dev/null
then
    echo "Change freeze in effect. Aborting deploy." >&2
    exit 1
fi

(This works with UTC timestamps, the ones ending in Z. Events with a TZID come through as local time with a separate dtstart_tzid field.)

Full write-up, which also covers headerless CSV/TSV files: JC Tips and Tricks – Part 3

What file formats are you still parsing by hand in your scripts?


r/bash • • 9d ago

rethinking bash2json: introducing shelljq

Thumbnail github.com
19 Upvotes

I took your feedback on bash2json, (wasted another year in local testing), and made: shelljq, new JSON parser for shell
it's built on bash2json's json_to_arr function as core for most operations, as well as:
- new name
- ZSH support (+ macOS)
- deeper integration inside scripts (including autogenerated functions documentation)
- own storing format for nested objects
- more powerful typechecking and JSON validation
- completely reworked `json_trim` for the best performance
- testing suite

shelljq is something I personally use in my projects (e.g. upcoming bashcord v2) and had high hopes for reliable tool which can be integrated into any script

shjq is cross-shell for both bash and zsh

I'm open to any critique, this project is still alpha and may still contain many bugs

let me know if i should provide usage examples, i wasn't sure on where to put them


r/bash • • 10d ago

Is this possible? Want to use terminal to show Youtube homepage with slightly better ux.

3 Upvotes

New to Linux. Installed Ubuntu. Keep reloading youtube home page because recommendations suck. Want to to avoid shorts and other unneeded features by using Youtube solely through bash. Want to know if small quality of life changes can be made.

I'm wondering if it's possible to use the terminal to show a small number of videos from my home page, title with no thumbnail is enough, which shows dislike and like count, make it so that pressing not interested or don't recommend channel is a bit faster to click.

Sorry if this is a weird or absurd ask. I'm going cold turkey on AI overview, but want to see if there should be anything I need to know about starting a project like this.


r/bash • • 11d ago

Unset doesn't properly work in functions??

9 Upvotes

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 • • 13d ago

is there a way to improve a github release updater to pick the right asset name?

13 Upvotes

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 • • 13d ago

Tinybackup: a tiny Kopia backup wrapper

4 Upvotes

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 • • 15d ago

critique Many things could go wrong with this script and leave a running process, how would you recommend fixing this?

17 Upvotes

```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 • • 17d ago

Can AI actually write production-ready scripts end-to-end without human intervention?

Thumbnail
0 Upvotes

r/bash • • 19d ago

radiosh – a small radio player for your terminal

Post image
9 Upvotes

r/bash • • 19d ago

fm: a single-file bash file manager — fzf picker, gallery grid, trash undo

Post image
56 Upvotes

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.

![demo](https://raw.githubusercontent.com/eHab-codeX/fm/main/docs/demo.gif)

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_list sort pipeline — it does one batched stat and one awk pass, then sort -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 .trashinfo format's Path= 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 • • 19d ago

Script for making .sh files executable

0 Upvotes

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 • • 21d ago

tips and tricks TIL command_not_found_handle

15 Upvotes

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 • • 22d ago

help For some reason, when I run this script, it always plays the same song.

Post image
33 Upvotes

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 • • 24d ago

Some Tips for String Handling

23 Upvotes

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 • • 24d ago

Ever wanted to attach to a background process and monitor stdout?

15 Upvotes

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 • • 26d ago

tips and tricks Editing a bash script while it's running gives you errors from lines that don't exist

16 Upvotes

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 • • 27d ago

File for loop

22 Upvotes

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 !