<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Bash Script &#8211; Everything is Broken</title>
	<atom:link href="https://play.datalude.com/blog/category/bash-script/feed/" rel="self" type="application/rss+xml" />
	<link>https://play.datalude.com/blog</link>
	<description>Efficiency vs. Inefficiency, in a no-holds barred fight.</description>
	<lastBuildDate>Fri, 06 Mar 2026 09:17:10 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>
	<item>
		<title>Removing all 5.xx kernels and associated modules, config from Ubuntu</title>
		<link>https://play.datalude.com/blog/2026/03/removing-all-5-xx-kernels-and-associated-modules-config-from-ubuntu/</link>
					<comments>https://play.datalude.com/blog/2026/03/removing-all-5-xx-kernels-and-associated-modules-config-from-ubuntu/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Fri, 06 Mar 2026 09:05:36 +0000</pubDate>
				<category><![CDATA[Bash Script]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://play.datalude.com/blog/?p=797</guid>

					<description><![CDATA[I was getting a bit cramped for space on the root partition of an older desktop, which has been running for 4 years or so, and discovered that although I'd been running the 6.x kernel for several years, there was a lot of baggage from 5.x kernels lying around. Here's a reminder to myself how ... <a title="Removing all 5.xx kernels and associated modules, config from Ubuntu" class="read-more" href="https://play.datalude.com/blog/2026/03/removing-all-5-xx-kernels-and-associated-modules-config-from-ubuntu/" aria-label="Read more about Removing all 5.xx kernels and associated modules, config from Ubuntu">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I was getting a bit cramped for space on the root partition of an older desktop, which has been running for 4 years or so, and discovered that although I'd been running the 6.x kernel for several years, there was a lot of baggage from 5.x kernels lying around. Here's a reminder to myself how to clear it up. If you've done an inplace upgrade from Ubuntu 22 to 24, you're probably in the same position. <br><br>So first of all, check your running kernel. You definitely want to keep that one! Then check what kernels you've got installed</p>



<pre class="wp-block-code"><code># Current kernel
uname -r
6.8.0-101-generic
# What kernels do we have installed. 
# ii at the beginning of the line means installed, but rc entries might have some residual config files.
dpkg --list | grep 'linux-image-'</code></pre>



<p class="wp-block-paragraph">Now, if you're certain that you want to get rid of all the kernels beginning with 5.x, then go ahead and run the following. Or otherwise edit the commands so that you're disposing of the ones you want to get. </p>



<pre class="wp-block-code"><code>sudo apt remove $(dpkg --list | grep 'linux-image-5\.' | awk '{print $2}' | xargs)  
# And now we get rid of all the associated modules and headers
sudo apt remove $(dpkg --list | grep 'linux-headers-5\.' | awk '{print $2}' | xargs)
sudo apt remove $(dpkg --list | grep 'linux-modules-5\.' | awk '{print $2}' | xargs)   
sudo apt remove $(dpkg --list | grep 'linux-modules-extra-5\.' | awk '{print $2}' | xargs)
# And clean up
sudo apt autoremove --purge
sudo update-grub   </code></pre>



<p class="wp-block-paragraph">So that cleared over a Gigabyte. Ubuntu will generally (I think) keep the last 3 kernels, but sometimes it doesn't seem to clean up everything it can. </p>
]]></content:encoded>
					
					<wfw:commentRss>https://play.datalude.com/blog/2026/03/removing-all-5-xx-kernels-and-associated-modules-config-from-ubuntu/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Bash script to replicate a directory structure</title>
		<link>https://play.datalude.com/blog/2025/12/bash-script-to-replicate-a-directory-structure/</link>
					<comments>https://play.datalude.com/blog/2025/12/bash-script-to-replicate-a-directory-structure/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Wed, 10 Dec 2025 06:17:08 +0000</pubDate>
				<category><![CDATA[Bash Script]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://play.datalude.com/blog/?p=789</guid>

					<description><![CDATA[I found a need for this once upon a time. It will descend through a Linux directory, get the permissions, ownership details for all the subdirs and then write a script for you to recreate it somewhere else. You use it like this. Script listing]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I found a need for this once upon a time. It will descend through a Linux directory, get the permissions, ownership details for all the subdirs and then write a script for you to recreate it somewhere else. </p>



<p class="wp-block-paragraph">You use it like this. </p>



<pre class="wp-block-code"><code># Put the script in the parent directory of the one you want to clone and run it.
clone_directory_structure.sh targetdirectory &gt; regenerate_directory_structure.sh

# Then move the script to where you want to clone the directory and run it. 
regenerate_directory_structure.sh 
</code></pre>



<p class="wp-block-paragraph">Script listing</p>



<pre class="wp-block-code"><code>#!/bin/bash

# Check if directory is provided
if &#91; $# -ne 1 ]; then
    echo "Usage: $0 &lt;directory>"
    echo "Example: $0 /path/to/directory"
    exit 1
fi

# Remove trailing slash if present
TARGET_DIR="${1%/}"
TARGET_BASENAME="$(basename "$TARGET_DIR")"
TARGET_PARENT="$(dirname "$TARGET_DIR")"

# Check if directory exists
if &#91; ! -d "$TARGET_DIR" ]; then
    echo "Error: Directory '$TARGET_DIR' does not exist"
    exit 1
fi

echo "#!/bin/bash"
echo "# Script generated on $(date)"
echo "# To recreate the directory structure, run this script as root or with sudo"
echo "set -e  # Exit on error"
echo

# Process the target directory itself
echo "# Creating target directory: $TARGET_BASENAME"
echo "mkdir -p \"$TARGET_BASENAME\""

# Get permissions, owner, and group of the target directory
TARGET_PERMS=$(stat -c "%a" "$TARGET_DIR")
TARGET_OWNER=$(stat -c "%U" "$TARGET_DIR")
TARGET_GROUP=$(stat -c "%G" "$TARGET_DIR")

echo "chmod $TARGET_PERMS \"$TARGET_BASENAME\""
echo "chown $TARGET_OWNER:$TARGET_GROUP \"$TARGET_BASENAME\""
echo

# Find all subdirectories and process them
find "$TARGET_DIR" -type d -not -path "$TARGET_DIR" -print0 | while IFS= read -r -d $'\0' dir; do
    # Quote the directory path to handle spaces
    dir="$dir"
    # Get relative path from target directory
    rel_path="${dir#$TARGET_DIR/}"
    
    # Quote the path to handle spaces
    rel_path_quoted="$(printf '%q' "$rel_path")"
    
    # Get permissions in octal
    perms=$(stat -c "%a" "$dir")
    
    # Get owner and group
    owner=$(stat -c "%U" "$dir")
    group=$(stat -c "%G" "$dir")
    
    # Output commands with proper quoting for paths with spaces
    echo "# Creating directory: $TARGET_BASENAME/$rel_path"
    echo "mkdir -p $(printf '%q' "$TARGET_BASENAME/$rel_path")"
    echo "chmod $perms $(printf '%q' "$TARGET_BASENAME/$rel_path")"
    echo "chown $owner:$group $(printf '%q' "$TARGET_BASENAME/$rel_path")"
    echo
done

echo "echo \"Directory structure recreation complete!\""
echo "echo \"Created structure under: $TARGET_BASENAME/\""
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://play.datalude.com/blog/2025/12/bash-script-to-replicate-a-directory-structure/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>bash htaccess file management script</title>
		<link>https://play.datalude.com/blog/2025/11/bash-htaccess-file-management-script/</link>
					<comments>https://play.datalude.com/blog/2025/11/bash-htaccess-file-management-script/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Thu, 06 Nov 2025 02:23:24 +0000</pubDate>
				<category><![CDATA[Bash Script]]></category>
		<category><![CDATA[General IT]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://play.datalude.com/blog/?p=780</guid>

					<description><![CDATA[OK, so its not too hard to run htpasswd manually, but if you spend a lot of time in your job doing it, it's nice to have a tool to do it more efficiently. This is a menu driven script, which will make a backup of your file, and suggest a random password, which you ... <a title="bash htaccess file management script" class="read-more" href="https://play.datalude.com/blog/2025/11/bash-htaccess-file-management-script/" aria-label="Read more about bash htaccess file management script">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">OK, so its not too hard to  run htpasswd manually, but if you spend a lot of time in your job doing it, it's nice to have a tool to do it more efficiently. This is a menu driven script, which will make a backup of your file, and suggest a random password, which you can choose to use or not. </p>



<p class="wp-block-paragraph">As always, don't trust everything you read on the internet, and test before you use in anger. </p>



<pre class="wp-block-code"><code>#!/bin/bash

# Configuration
HTACCESS_FILE_DEFAULT="/etc/nginx/htpasswd-developers

# Function to display a list of users
display_users() {
  echo "--- Existing Users ---"
  # Grep for lines that don't start with # and aren't empty
  # Then use cut to show only the username before the colon, and number the lines
  grep -vE '^(#|$)' "$HTACCESS_FILE" | cut -d':' -f1 | cat -n
  echo "----------------------"
}

# Function to perform a backup
backup_file() {
  if &#91; -f "$HTACCESS_FILE" ]; then
    cp "$HTACCESS_FILE" "$HTACCESS_FILE.bak"
    echo "Backup created at $HTACCESS_FILE.bak"
  else
    echo "No .htpasswd file to back up."
  fi
}

# Function to generate a random password
generate_password() {
    tr -cd '&#91;:alnum:]' &lt; /dev/urandom | head -c 12
}

# --- Main Script ---

# Select the .htpasswd file. Suggest the default value, but allow user to type another name
read -p "Enter .htpasswd file (default: $HTACCESS_FILE_DEFAULT): " HTACCESS_FILE
HTACCESS_FILE=${HTACCESS_FILE:-$HTACCESS_FILE_DEFAULT}

# Ensure the file exists, exit if not
if &#91; ! -f "$HTACCESS_FILE" ]; then
  echo "Password file doesn't exist"
  exit 1
fi

# Main menu loop
while true; do
  echo "Do you want to:"
  echo "a) Add a user"
  echo "r) Remove a user"
  echo "u) Update a user's password"
  echo "l) List users"
  echo "q) Quit"
  read -p "Enter your choice: " choice

  case "$choice" in
    a)
      read -p "Enter username to add: " username
      # Check if the username already exists
      if grep -q "^$username:" "$HTACCESS_FILE"; then
        echo "Error: User '$username' already exists. Use the 'u' option to update their password."
      else
        suggested_password=$(generate_password)
        read -p "Enter password for '$username' (or press Enter to use suggested: $suggested_password): " password
        password=${password:-$suggested_password}
        
        backup_file
        htpasswd -b "$HTACCESS_FILE" "$username" "$password"
        echo "User '$username' added."
      fi
      ;;
    r)
      display_users
      read -p "Enter reference number of user to remove: " ref
      # Use grep and sed to find the line number and get the username
      username_to_remove=$(grep -vE '^(#|$)' "$HTACCESS_FILE" | sed -n "${ref}p" | cut -d':' -f1)

      if &#91; -z "$username_to_remove" ]; then
        echo "Invalid reference number."
      else
        backup_file
        # Create a temp file without the user and then replace the original
        grep -v "^$username_to_remove:" "$HTACCESS_FILE" > "$HTACCESS_FILE.tmp" &amp;&amp; mv "$HTACCESS_FILE.tmp" "$HTACCESS_FILE"
        echo "User '$username_to_remove' removed."
      fi
      ;;
    u)
      display_users
      read -p "Enter reference number of user to update: " ref
      username_to_update=$(grep -vE '^(#|$)' "$HTACCESS_FILE" | sed -n "${ref}p" | cut -d':' -f1)

      if &#91; -z "$username_to_update" ]; then
        echo "Invalid reference number."
      else
        suggested_password=$(generate_password)
        read -p "Enter new password for '$username_to_update' (or press Enter to use suggested: $suggested_password): " password
        password=${password:-$suggested_password}

        backup_file
        htpasswd -b "$HTACCESS_FILE" "$username_to_update" "$password"
        echo "Password for user '$username_to_update' updated."
      fi
      ;;
    l)
      display_users
      ;;
    q)
      echo "Exiting."
      exit 0
      ;;
    *)
      echo "Invalid option. Please try again."
      ;;
  esac

  echo ""
done
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://play.datalude.com/blog/2025/11/bash-htaccess-file-management-script/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>HEREDOC operators</title>
		<link>https://play.datalude.com/blog/2025/09/heredoc-operators/</link>
					<comments>https://play.datalude.com/blog/2025/09/heredoc-operators/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Wed, 24 Sep 2025 06:41:25 +0000</pubDate>
				<category><![CDATA[Bash Script]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://play.datalude.com/blog/?p=767</guid>

					<description><![CDATA[Basic usage Output alternatives: Can also use >> to append to output file, or &#124; tee to output to screen as it runs. Quoting preserves content If you're outputting a script you might need to preserve the variable notation instead of evaluating it. You can use either single or double quotes to do this. Zapping ... <a title="HEREDOC operators" class="read-more" href="https://play.datalude.com/blog/2025/09/heredoc-operators/" aria-label="Read more about HEREDOC operators">Read more</a>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">Basic usage</h2>



<pre class="wp-block-code"><code>VARIABLE="variable"

# command format
cat &lt;&lt; CONTENTS > /path/to/output.conf
Normal text here 
The second line contains a $VARIABLE
        These are Tabbed
        Indented
CONTENTS

# Result, cat /path/to/output.conf
Normal text here
The second line contains a variable
        These are Tabbed
        Indented</code></pre>



<p class="wp-block-paragraph"><strong>Output alternatives:</strong> Can also use >> to append to output file, or | tee to output to screen as it runs. </p>



<h2 class="wp-block-heading">Quoting preserves content</h2>



<p class="wp-block-paragraph">If you're outputting a script you might need to preserve the variable notation instead of evaluating it. You can use either single or double quotes to do this. </p>



<pre class="wp-block-code"><code>VARIABLE="variable"

# command format
cat &lt;&lt; 'CONTENTS' > /path/to/output.conf
Normal text here 
The second line contains a $VARIABLE
        These are Tabbed
        Indented
CONTENTS

# Result, cat /path/to/output.conf
Normal text here 
The second line contains a $VARIABLE
        These are Tabbed
        Indented
</code></pre>



<h2 class="wp-block-heading">Zapping Tabs</h2>



<p class="wp-block-paragraph">The &lt;&lt;- construct will remove tabs from your input, which means you can format your code nicely. Doesn't work at the same time as "CONTENTS"<br><br></p>



<pre class="wp-block-code"><code>VARIABLE="variable"

# command format
cat &lt;&lt;- CONTENTS > /path/to/output.conf
Normal text here 
The second line contains a $VARIABLE
	These are Tabbed
		Indented
CONTENTS

# Result, cat /path/to/output.conf
Normal text here 
The second line contains a variable
These are Tabbed
Indented
</code></pre>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://play.datalude.com/blog/2025/09/heredoc-operators/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Advanced Bash logging to handle errors.</title>
		<link>https://play.datalude.com/blog/2025/09/advanced-bash-logging-to-handle-errors/</link>
					<comments>https://play.datalude.com/blog/2025/09/advanced-bash-logging-to-handle-errors/#comments</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Mon, 01 Sep 2025 07:27:41 +0000</pubDate>
				<category><![CDATA[Bash Script]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[script]]></category>
		<guid isPermaLink="false">https://play.datalude.com/blog/?p=759</guid>

					<description><![CDATA[So we've all done a quick log from a bash script, which looks like this That's great for small scripts. But sometimes you find that its not logging errors, which are also helpful, and you need to add those: And different commands behave differently, so it all starts to become complicated. But there's an easier ... <a title="Advanced Bash logging to handle errors." class="read-more" href="https://play.datalude.com/blog/2025/09/advanced-bash-logging-to-handle-errors/" aria-label="Read more about Advanced Bash logging to handle errors.">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">So we've all done a quick log from a bash script, which looks like this </p>



<pre class="wp-block-code"><code>LOGFILE="logs/$(date +%F)_backup.log"
# Append to the file
restic backup /etc/ &gt;&gt; $LOGFILE
find /root/scripts/ -type f -name "restic*.log" -mtime +14 -delete -print &gt;&gt; $LOGFILE</code></pre>



<p class="wp-block-paragraph">That's great for small scripts. But sometimes you find that its not logging errors, which are also helpful, and you need to add those:</p>



<pre class="wp-block-code"><code>LOGFILE="logs/$(date +%F)_backup.log"
# Append to the file
restic backup /etc/ 2&gt;&amp;1 &gt;&gt; $LOGFILE
find /root/scripts/ -type f -name "restic*.log" -mtime +14 -delete -print 2&gt;&amp;1 &gt;&gt; $LOGFILE</code></pre>



<p class="wp-block-paragraph">And different commands behave differently, so it all starts to become complicated. But there's an easier way. Two actually. The first is to use curly brackets to contain all the commands you want to log, and then pipe it through tee at the end. Commands after that block won't be logged. </p>



<span id="more-759"></span>



<pre class="wp-block-code"><code>LOGFILE="logs/$(date +%F)_backup.log"
{
echo "Starting Restic backup at $(date)"
restic backup /etc/
find /root/scripts/ -type f -name "restic*.log" -mtime +14 -delete -print
echo "Restic backup and maintenance completed"
} 2>&amp;1 | tee -a "${LOGFILE}"</code></pre>



<p class="wp-block-paragraph">And then there's the slightly less readable command substitution method. </p>



<pre class="wp-block-code"><code>exec &gt; &gt;(tee -a "${LOGFILE}") 2&gt;&amp;1
echo "Starting Restic backup at $(date)"
$RESTIC backup /etc/
find /root/scripts/ -type f -name "restic*.log" -mtime +14 -delete -print
echo "Restic backup and maintenance completed"</code></pre>



<h3 class="wp-block-heading">Comparison</h3>



<p class="wp-block-paragraph">Both methods will log all output of all commands, including errors. I find the curly brackets way slightly easier to read. It also allows you to select which commands are logged, but putting them inside the brackets or not. On the other hand, the exec method will log everything until the end of the script, which may or may not be desirable. Curly braces also seems to be more widely compatible. </p>



<p class="wp-block-paragraph">There may be other considerations, different performance considerations and buffering for heavy duty scripts, but that's beyond my regular use cases. </p>



<p class="wp-block-paragraph">Will just add a poor-man's log rotation trick for compleness, and so I can copy and paste it. </p>



<pre class="wp-block-code"><code># Start logging to rolling logfile
LOGFILE=/path/to.log
KEEPLINES=5000
{
            ### commands
} 2>&amp;1 | tee -a "${LOGFILE}"

# Keep last XXXX lines of rolling logfile, defined above in KEEPLINES
tail -n $KEEPLINES "$LOGFILE" > "$LOGFILE".tmp
mv -f "$LOGFILE".tmp "$LOGFILE"</code></pre>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://play.datalude.com/blog/2025/09/advanced-bash-logging-to-handle-errors/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Bash script for managing htaccess files</title>
		<link>https://play.datalude.com/blog/2025/08/bash-script-for-managing-htaccess-files/</link>
					<comments>https://play.datalude.com/blog/2025/08/bash-script-for-managing-htaccess-files/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Wed, 20 Aug 2025 05:19:04 +0000</pubDate>
				<category><![CDATA[Bash Script]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://play.datalude.com/blog/?p=752</guid>

					<description><![CDATA[Had to create this for a less technical user to manage htaccess, so thought I'd share. Your mileage may vary. Change the location of the default htpass file in the config. You can supply another location in mid script if you have more than one. #!/bin/bash# ConfigurationHTACCESS_FILE_DEFAULT="htpass_test"# Function to display a list of usersdisplay_users() { ... <a title="Bash script for managing htaccess files" class="read-more" href="https://play.datalude.com/blog/2025/08/bash-script-for-managing-htaccess-files/" aria-label="Read more about Bash script for managing htaccess files">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Had to create this for a less technical user to manage htaccess, so thought I'd share. Your mileage may vary. <br><br>Change the location of the default htpass file in the config. You can supply another location in mid script if you have more than one. </p>



<pre class="wp-block-preformatted">#!/bin/bash<br><br># Configuration<br>HTACCESS_FILE_DEFAULT="htpass_test"<br><br># Function to display a list of users<br>display_users() {<br>  echo "--- Existing Users ---"<br>  # Grep for lines that don't start with # and aren't empty<br>  # Then use cut to show only the username before the colon, and number the lines<br>  grep -vE '^(#|$)' "$HTACCESS_FILE" | cut -d':' -f1 | cat -n<br>  echo "----------------------"<br>}<br><br># Function to perform a backup<br>backup_file() {<br>  if [ -f "$HTACCESS_FILE" ]; then<br>    cp "$HTACCESS_FILE" "$HTACCESS_FILE.bak"<br>    echo "Backup created at $HTACCESS_FILE.bak"<br>  else<br>    echo "No .htpasswd file to back up."<br>  fi<br>}<br><br># Function to generate a random password<br>generate_password() {<br>    tr -cd '[:alnum:]' &lt; /dev/urandom | head -c 12<br>}<br><br># --- Main Script ---<br><br># Select the .htpasswd file<br>read -p "Enter .htpasswd file (default: $HTACCESS_FILE_DEFAULT): " HTACCESS_FILE<br>HTACCESS_FILE=${HTACCESS_FILE:-$HTACCESS_FILE_DEFAULT}<br><br># Ensure the file exists, exit if not<br>if [ ! -f "$HTACCESS_FILE" ]; then<br>  echo "Password file doesn't exist"<br>  exit 1<br>fi<br><br># Main menu loop<br>while true; do<br>  echo "Do you want to:"<br>  echo "a) Add a user"<br>  echo "r) Remove a user"<br>  echo "u) Update a user's password"<br>  echo "l) List users"<br>  echo "q) Quit"<br>  read -p "Enter your choice: " choice<br><br>  case "$choice" in<br>    a)<br>      read -p "Enter username to add: " username<br>      # Check if the username already exists<br>      if grep -q "^$username:" "$HTACCESS_FILE"; then<br>        echo "Error: User '$username' already exists. Use the 'u' option to update their password."<br>      else<br>        suggested_password=$(generate_password)<br>        read -p "Enter password for '$username' (or press Enter to use suggested: $suggested_password): " password<br>        password=${password:-$suggested_password}<br>        <br>        backup_file<br>        htpasswd -b "$HTACCESS_FILE" "$username" "$password"<br>        echo "User '$username' added."<br>      fi<br>      ;;<br>    r)<br>      display_users<br>      read -p "Enter reference number of user to remove: " ref<br>      # Use grep and sed to find the line number and get the username<br>      username_to_remove=$(grep -vE '^(#|$)' "$HTACCESS_FILE" | sed -n "${ref}p" | cut -d':' -f1)<br><br>      if [ -z "$username_to_remove" ]; then<br>        echo "Invalid reference number."<br>      else<br>        backup_file<br>        # Create a temp file without the user and then replace the original<br>        grep -v "^$username_to_remove:" "$HTACCESS_FILE" > "$HTACCESS_FILE.tmp" &amp;&amp; mv "$HTACCESS_FILE.tmp" "$HTACCESS_FILE"<br>        echo "User '$username_to_remove' removed."<br>      fi<br>      ;;<br>    u)<br>      display_users<br>      read -p "Enter reference number of user to update: " ref<br>      username_to_update=$(grep -vE '^(#|$)' "$HTACCESS_FILE" | sed -n "${ref}p" | cut -d':' -f1)<br><br>      if [ -z "$username_to_update" ]; then<br>        echo "Invalid reference number."<br>      else<br>        suggested_password=$(generate_password)<br>        read -p "Enter new password for '$username_to_update' (or press Enter to use suggested: $suggested_password): " password<br>        password=${password:-$suggested_password}<br><br>        backup_file<br>        htpasswd -b "$HTACCESS_FILE" "$username_to_update" "$password"<br>        echo "Password for user '$username_to_update' updated."<br>      fi<br>      ;;<br>    l)<br>      display_users<br>      ;;<br>    q)<br>      echo "Exiting."<br>      exit 0<br>      ;;<br>    *)<br>      echo "Invalid option. Please try again."<br>      ;;<br>  esac<br><br>  echo # Add a newline for spacing<br>done</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://play.datalude.com/blog/2025/08/bash-script-for-managing-htaccess-files/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
