r_bash – Telegram
Just looking for general suggestions about my bootstrap noscript

Hi, I'm not looking for any advice in particular, just want to share my noscript and learn from people more experienced than me.

It's just a noscript I wrote some time ago to install some packages on my other Linux machines that evolved into a bootstrap for my system. Let me know what you think about it.

Here is the noscript

https://redd.it/1khfjq7
@r_bash
More Stupid Associative Array Tricks with Dynamic Array Names (Tiny Database)

Here's a somewhat contrived example of using named references and also using dynamically created variables - sort fo like an array of associative arrays. It also simulates daat entry from a terminal and will also run using terminal daat entered by hand, but it shows a good mix of named references and also dynamic variable definition, wihch i use a fair amout when getting variables set in side a configuration file such as:

options="-a -b -c"
directory="${HOME}/data"
file="some_data_file.data"

I can read the config file and set dynamic variables using the names. Reading and splitting them with a read and using IFS='=', rather than using an eval. I can also give them values by doing normal variable expansion using an echo:

declare ${config_var}=$( echo "${rvalue}" )

Anyway here's a fun little (well, kinda long with comments, maybe overengineered too) demo noscript I hacked together to show some os the dynamic naming and also using the `local -n` along with `${!variable}`.

#!/usr/bin/env bash
#------------------------------------------------------------------------------
# Bash Dynamic Array Names in Memory Database Example
#------------------------------------------------------------------------------
# This noscript demonstrates advanced Bash programming concepts by implementing
# a simple in-memory database using arrays. Key concepts demonstrated include:
#
# 1. Dynamic Variable Names
# - Uses bash's indirect reference capabilities
# - Shows how to create and manage variables dynamically
# - Demonstrates proper use of 'declare' for array creation
#
# 2. Associative Arrays
# - Each record is stored as an associative array (person_N)
# - Shows how to properly initialize and manage associative arrays
# - Demonstrates key-value pair storage and retrieval
#
# 3. Name References (nameref)
# - Uses 'declare -n' for creating references to arrays
# - Shows proper scoping and cleanup of namerefs
# - Demonstrates why namerefs need to be recreated in loops
#
# 4. Record Management
# - Implements basic CRUD operations (Create, Read, Update, Delete)
# - Uses a status array (person_index) to track record state
# - Shows soft-delete functionality (marking records as deleted)
#
# 5. Input Handling
# - Demonstrates file denoscriptor manipulation
# - Shows how to handle both interactive and automated input
# - Implements proper input validation
#
# Usage Examples:
# ./test -i # Interactive mode: Enter data manually
# ./test -t # Test mode: Uses predefined test data
#
# Database Structure:
# person_N - Associative array for each record (N = index)
# person_index - Tracks record status (E=exists, D=deleted)
# person_attributes - Defines the schema (field names)
# person_attr_display - Maps internal names to display names


# Will store state of each person record
# E = active employee, D = deleted employee
# Other flags could be added for indicating other states
declare -a person_index=()

# Define the attributes each person record will have
# This array defines the "schema" for our person records
# Simply add an attribute name to extend the table
declare -a person_attributes=(
"employee_id" # Unique identifier
"LastName" # Family name
"FirstName" # Given name
"email" # Contact email
)

# Display name mapping for prettier output
declare -A person_attr_display=(
[employee_id]="Employee ID"
[LastName]="Last Name"
[FirstName]="First Name"
[email]="Email"
)

# Test data for demonstration purposes and simulating user terminal input
TEST_DATA=$(cat << 'DATA'
Doe
John
john.doe@example.com
y
Smith
Jane
jane.smith@example.com
y
Johnson
Robert
robert.johnson@example.com
y
Williams
Mary
mary.williams@example.com
y
Brown
James
james.brown@example.com
n
DATA
)

# Function to generate unique employee IDs
# Combines the record index with a random number to ensure uniqueness
# Args: $1 - The record index (1-based)
generate_employee_number() {
printf "%d%06d" "$(( $1 + 1 ))" "$((RANDOM % 1000000))"
}

# Function to get the current number of records
# Used for both array sizing and new record creation
get_index() {
local current_idx
current_idx=${#person_index[@]}
echo "$current_idx"
}

# Function to create a new person record
# Args: $1 - The index for the new record
# Creates a new associative array and marks it as active
create_person() {
local current_idx=$1
declare -gA "person_${current_idx}"
person_index+=("E")
}

# Function to convert from 1-based (user) index to 0-based (internal) index
# Args: $1 - User-facing index (1-based)
# Returns: Internal array index (0-based) or -1 if invalid
to_internal_index() {
local user_idx=$1
if [[ "$user_idx" =~ ^[1-9][0-9]*$ ]] && ((user_idx <= $(get_index))); then
echo "$((user_idx - 1))"
else
echo "-1"
fi
}

# Function to mark a record as deleted
# Implements soft-delete by setting status flag to 'D'
# Args: $1 - User-facing index (1-based)
delete_person() {
local user_idx=$1
local internal_idx

internal_idx=$(to_internal_index "$user_idx")
if [[ $internal_idx -ge 0 ]]; then
person_index[$internal_idx]="D"
return 0
else
echo "Error: Invalid person number $user_idx" >&2
return 1
fi
}

# Function to check if a record exists and is active
# Args: $1 - Internal index (0-based)
# Returns: true if record exists and is active, false otherwise
is_person_active() {
local idx=$1
[[ $idx -lt $(get_index) && "${person_index[$idx]}" == "E" ]]
}

# Function to update a person's attribute
# Uses nameref to directly modify the associative array
# Args: $1 - Array name to update
# $2 - Attribute name
# $3 - New value
update_person_attribute() {
local -n person_array_name=$1
local attr=$2
local value=$3

person_array_name[$attr]="$value"
}

# Function to display all active person records
# Demonstrates:
# - Proper nameref handling in loops
# - Format string usage for consistent output
# - Conditional record filtering (skipping deleted)
display_people() {
local fmt=" %-12s: %s\n"
local separator="------------------------"
local report_separator="\n$separator\n%s\n$separator\n"

printf "\n$report_separator" "Active Personnel Records"

for idx in "${!person_index[@]}"; do
# Skip if person is marked as deleted
! is_person_active "$idx" && continue

printf "$report_separator" "Person $((idx+1))"

# Create new nameref for each iteration to ensure proper binding
local -n person="person_${idx}"

# Display attributes with proper labels
for attr in "${person_attributes[@]}"; do
local display_name="${person_attr_display[$attr]:-$attr}"
local value
value="${person[$attr]}"
printf "$fmt" "$display_name" "$value"
done
done

printf "$report_separator\n" "End of Report"
}

# Function to handle data entry for a new person
# Args: $1 - File denoscriptor to read input from
# Demonstrates:
# - File denoscriptor manipulation for input
# - Dynamic array creation and population
# - Proper error checking and validation
enter_data()
{
local fd=$1
local current_index

while true; do
current_index=$(get_index)
create_person "$current_index"

# Create a reference to the current person's associative array
declare -n current_person="person_${current_index}"

# Set employee ID
current_person[employee_id]=$(generate_employee_number "$((current_index + 1))")

# Read other attributes
for attr in "${person_attributes[@]}"; do
local display_name="${person_attr_display[$attr]:-$attr}"
case "$attr" in
"employee_id") continue ;;
esac
read -u "$fd" -p "Enter $display_name: " value

if [[ $? -eq 0 ]]; then
update_person_attribute "person_${current_index}" "$attr" "$value"
fi
done

if read -u "$fd" -p "Add another person? (y/n): " continue; then
[[ $continue != "y" ]] && break
else
break
fi
done
}

# Function to run in test mode with predefined data
test_mode() {
echo "Running in test mode with dummy data..."
# Create temporary file denoscriptor (3) for test data
exec 3< <(echo "$TEST_DATA")
enter_data 3
exec 3<&- # Close the temporary file denoscriptor
}

# Function to run in interactive mode with user input
interactive_mode() {
echo "Running in interactive mode..."
enter_data 0 # Use standard input (fd 0)
}

# Main noscript logic
case "$1" in
"-t")
test_mode
;;
"-i")
interactive_mode
;;
*)
echo "Usage: $0 [-t|-i]"
echo " -t Run with test data"
echo " -i Run with terminal input"
exit 1
;;
esac

# Display all active records
display_people

# Demonstrate "deleting" records by changing their status
echo "Deleting records employee number 2 and number 4"
delete_person 2 # Mark second person as deleted
delete_person 4 # Mark fourth person as deleted

# Display again - deleted records won't show
display_people

echo
echo "Show the actual variable definitions, including the dynamic arrays"
declare -p | grep person

Here's the output:

(python-3.10-PA-dev) [unixwzrd@xanax: test]$ ./test -t
Running in test mode with dummy data...


------------------------
Active Personnel Records
------------------------

------------------------
Person 1
------------------------
Employee ID : 2027296
Last Name : Doe
First Name : John
Email : john.doe@example.com

------------------------
Person 2
------------------------
Employee ID : 3028170
Last Name : Smith
First Name : Jane
Email : jane.smith@example.com

------------------------
Person 3
------------------------
Employee ID : 4014919
Last Name : Johnson
First Name : Robert
Email : robert.johnson@example.com

------------------------
Person 4
------------------------
Employee ID : 5024071
Last Name : Williams
First Name : Mary
Email : mary.williams@example.com

------------------------
Person 5
------------------------
Employee ID : 6026645
Last Name : Brown
First Name : James
Email : james.brown@example.com

------------------------
End of Report
------------------------

Deleting records employee number 2 and number 4


------------------------
Active Personnel Records
------------------------

------------------------
Person 1
------------------------
Employee ID : 2027296
Last Name : Doe
First Name : John
Email : john.doe@example.com

------------------------
Person 3
------------------------
Employee ID : 4014919
Last Name : Johnson
First Name : Robert
Email : robert.johnson@example.com

------------------------
Person 5
------------------------
Employee ID : 6026645
Last Name : Brown
First Name : James
Email : james.brown@example.com

------------------------
End of Report
------------------------


Show the actual variable definitions, including the dynamic arrays
declare -A person_0=([FirstName]="John" [email]="john.doe@example.com [LastName]="Doe" [employee_id]="2027296" )
declare -A person_1=([FirstName]="Jane" [email]="jane.smith@example.com" [LastName]="Smith" [employee_id]="3028170" )
declare -A person_2=([FirstName]="Robert" [email]="robert.johnson@example.com" [LastName]="Johnson" [employee_id]="4014919" )
declare -A person_3=([FirstName]="Mary" [email]="mary.williams@example.com" [LastName]="Williams" [employee_id]="5024071" )
declare -A person_4=([FirstName]="James" [email]="james.brown@example.com" [LastName]="Brown" [employee_id]="6026645" )
declare -A person_attr_display=([FirstName]="First Name" [email]="Email" [LastName]="Last Name" [employee_id]="Employee ID" )
declare -a person_attributes=([0]="employee_id" [1]="LastName" [2]="FirstName" [3]="email")
declare -a person_index=([0]="E" [1]="D" [2]="E" [3]="D" [4]="E")

https://redd.it/1khh7xb
@r_bash
This media is not supported in your browser
VIEW IN TELEGRAM
I built this simple tool to hide folders on Linux using a password-protected CLI + TUI completely written in bash.
https://redd.it/1khrb3q
@r_bash
rsync: how do I get a list without dirs/ with no change into them?

Hi, I am understanding how to drive within Fat32: using -anchuv
(The permits are changed yes or yes in Fat32 and this does not happen in ext4, but anyway those differences do not generate a copy if I made a copy in reverse from destination to origin).

Now I see a list that scares, full of directories but without files under them.

This means that nothing of them will be copied. Then I would like those directories without changes ... Let's say that only files are displayed.

I made a screenshot in which you see that list of directories without files to copy ... https://imgbox.com/y4VTZtTX

I would like that list of meaningless directories.

Thank you and regards!

https://redd.it/1khu7zo
@r_bash
I found why rsync copy with time diff in FAT32 vs. copy without diff in ext4

hi I'd like to tell you what happend when I use FAT 32 copy with rsync vs. the same in ext4:

the clue is ls -l --full-time : there is a little diff in time of 1/2 second in sourc-dest. in FAT32 when I use rsync -anhuv.

When I use dirdiff there is green and red leaves... and diff shows nothing: the file is the same sourc. dest. but time is diff.: 1/2 sec. of time.

in ext4 rsync copy preserve full-time (and perm. too)

so in FAT32 I should use rsync -anchuv -c is mandatory!!!

in FAT32 -p and -t don't work! so -c is mandatory.

in ext4 -c isn't necesary: -p -t work fine.

that's all

Regards!

https://redd.it/1ki87j3
@r_bash
Javanoscript in BASH

I recently had a client that created a whole interpretative language add-on in a DOM object to allow Product Managers to influence backend code, so as not to require developers to write integrations. This seemed like so much of a fun idea, that I felt it required to create a minimalistic JavaScript interpreter to prove that, once again, BASH can pretty much do everything a "normal" development language can.

Yes. I know. I don't care. I had fun :)

https://github.com/elemantalcode/jash for the win!

https://redd.it/1kipfdy
@r_bash
Multiple sourcing issue

Hi, I have a problem in my project, the problem is in my library,

i have directory called lib, containing multiple .sh files

├── application
│   │
│   └── main.sh

├── lib
│   ├── arrays.sh
│   ├── files.sh
│   ├── math.sh
│   └── out.sh

in `out. sh` file I have some function for error handling, also contains some `readonly` variables , and other files in the library use the error handling function in `out. sh`

example:

`application/main.sh:`

source "../lib/out.sh"
source "../lib/array.sh"

do_something || error_handle .......

`lib/arrays.sh:`

source "out.sh"

func1(){
# some code
}

`lib/out.sh:`

readonly __STDOUT=1
readonly __STDERR=2
readonly __ERROR_MESSAGE_ENABLE=0

error_handle(){
# some code
}

now the problem is when I run the project, it tells me that the `$__STDOUT` and the other vars are `readonly` and cannot be modified, because the variables are declared twice, first by my app when it source the `out. sh` , second when `arrays. sh` source `out. sh` , so my question, how to solve it in the best way, I already think about make variables not read only, but I feel it is not the best way, because we still have multiple sourcing, and I want to keep all library functions to have access to error handling functions

https://redd.it/1kj9wya
@r_bash
How to better structure this gitfzf noscript

This is my new noscript for getting fzf list of all git repos in my home directory and applying git commands with respect to the the repo selected.

I want a better structure of this noscript minimizing the loc and adding more functionality.

#!/usr/bin/env bash

# use fzf for selecting the repos from the desktop
# and then select one, apply a command such as status, sync with remote, etc.

local
repo(){ # searches the .git dir in $1
if -z $REPOS ; then
export REPOS="$(dirname $(find $1 -name ".git" -type d))"
fi
echo $REPOS | tr ' ' '\n'
}

reposelection(){ # fzf selection from allrepo
local selected
local repo
repo=\localrepo $1``
selected=$( printf "$repo" | fzf +m --height 50% --style full --input-label ' Destination ' --preview 'tree -C {}')
echo $selected
}

git
fnc(){
gitcmda=( "branch_commit"="branch -vva" )
gitcmd=("status" "status -uno" "log" "branch -vva" "commit" "add" "config") # simple git commands
selected=$( printf '%s\n' "${git
cmd@}" | fzf +m --height 50% --style full --input-label ' Destination ' --preview 'tree -C {}')
echo $selected
}

repo=\reposelection $HOME``
cmd="cmd"

while [ ! -z "$cmd" ]; do
cmd=\git
fnc
if [ -z "$repo" || -z "$cmd" ]; then
exit 0
fi
printf "\n git -C $repo $cmd"
git -C $repo $cmd
done

https://redd.it/1kjei14
@r_bash
Installing Newer Versions of Bash with Mise

Hey all,

I'm working on some developer tooling for my company and want to make sure everyone is running the latest version of Bash when I'm noscripting tools.

I'm using Mise to pin various other languages, but don't see Bash support. Do you all have a good way of pinning the Bash version for other engineers so that when noscripts are run they use the correct version? Has anyone had success with Mise for this, preferably, or another method?

https://redd.it/1kkexhq
@r_bash
Visualize (convert) list of files as tree hiearchy

tree -afhDFci <dir> produces a list of files in the following format:

894 Apr 20 2024 /media/wd8000-1/music/unsorted/
2.0K Apr 20 2024 /media/wd8000-1/music/unsorted/A/
19M Apr 20 2024 /media/wd8000-1/music/unsorted/A/AA.mp4
...

I run this command on an external drive to save it into a text file before unplugging it so I can grep it to see what files I have on the drive along with basic metadata. It's an 8 TB drive with 20k+ files.

I would like a way to visualize the tree structure (e.g. understand how it's organized), e.g. convert it back to the standard tree command output with directory hierarchy and indentation. Or show list of directories that are X levels deep. Unfortunately I won't have access to the drives for a month so I can't simply save another tree output to another file (I also prefer to parse from one saved file).

Any tips? Another workaround would be parse the file and create the file tree as empty files on the filesystem but that wouldn't be efficient.

https://redd.it/1kkerqz
@r_bash
What's the weirdest or most unexpected thing you've automated with Bash?

If we don't count all sysadmin tasks, backups, and cron jobs... what's the strangest or most out-of-the-box thing you've noscripted?

I once rigged a Bash noscript + smart plug to auto-feed my cats while I was away.

https://redd.it/1kkpg7l
@r_bash
.config files in $XDGCONFIGHOME

This is not technically a bash question, but it's shell related and this place is full of smart people.

Let's say I'm writing a noscript that needs a .config file, but I want the location to be in $XDG_CONFIG_HOME/noscriptname.

Leading dots are great for reducing clutter, but that's not an issue if the file is in an uncluttered subdirectory

What's the accepted best practice on naming a config file that sits inside a config directory - with a leading dot or not? I don't see any advantages to leading dots in this case, but decades of noscripting tells me that config files start with a dot ;-)

Note: I'm interested in people's opinions, so please don't reply with a ChatGPT generated opinion

https://redd.it/1km1u0s
@r_bash
how to parallelize a for loop in batches?

On a Proxmox PVE host, I'd like to do scheduled live migrations. My "oneliner" already works, but I've got 2 NICs in adaptive-alb, so I can do 2 migrations at the same time, I presume nearly doubling the speed.

This oneliner will "serialize" the migrations of all VMs it finds on a host (except VM with VMID 120).

Question: how do I change the oneliner below so it does 2 parallel migrations, if those finish, continue with the next two VMs. Ideally, if one finishes, it could immediately start another migration, but it's OK if I can do 100, 101, wait, then 102, 103 wait, then 104 and 105, ... until all VMs are done.

time for vmid in $(qm list | awk '$3=="running" && $1!="120" { print $1 }'); do qm migrate $vmid pve3 --online --migration_network 10.100.80.0/24 --bwlimit 400000; done



https://redd.it/1km88ka
@r_bash
I'm thinking to start to work as a freelancer with shelll scrippting. Realistically, is there a market for that out there?

Just adding to the noscript: I'm thinking about doing this from home on sites like Upwork, Fiverr, **Freelancer.com**, Toptal, and PeoplePerHour. Working in an organization isn't an option at the moment.

I have some portfolio to show, but let's say the noscript's purposes are kind of shallow to show in this context, I'd guess. They're noscripts to help my day-to-day flow. The code itself is fine, most are pure-bash and performance-sensitive noscripts, but their purpose.. Anyway, if you guys say that's an issue, I can find projects to engage in. It's a good way to learn anyway, which is never enough. Thankfully, I have no rush to start working. I've been solely on Linux for around 10 years, and improving or switching my portfolio is not a huge deal.

https://redd.it/1kmdrxc
@r_bash
This media is not supported in your browser
VIEW IN TELEGRAM
[Update] Added Security for dotfold (Previously No Security)
https://redd.it/1kmqmbc
@r_bash
Advance a pattern of numbers incrementally

Hi, I am trying to advance a pattern of numbers incrementally.



The pattern is: 4 1 2 3 8 5 6 7



Continuing the pattern the digits should produce: 4,1,2,3,8,5,6,7,12,9,10,11,16,13,14,15... onwards etc.



What I am trying to archive is to print a book on A4 paper, 2 pages each side so that's 4 pages per sheet when folded and then bind it myself. I have a program that can rearrange pages in a PDF but I have to feed it the correct sequence and I am not able to do this via the printer settings for various reasons hence setting up the PDF page order first. I know I can increment a simple sequence in using something like:



for i in \seq -s, 1 1 100; do echo $i; done

But obviously I am missing the the important arithmetic bits in between to repeat the pattern



Start with: 4

take the 1st input and: -3

take that last input +1

take that last input +1

take that last input +5 etc etc



I am not sure how to do this.



Thanks!

https://redd.it/1kn6ozs
@r_bash