Handy scripts you'd like to share


I had a bit of time to kill yesterday so I thought I'd write a minor script to correct something that was bothering me lately. Then I thought it might be a good opportunity to start up a permanent thread to share useful scripts members may have written.


So, I'll go first.


The following script is to prevent an external USB hard drive from sleeping.

This simple script will keep an external USB platter drive from spinning down and going to sleep. Some external USB drive enclosures do not respect the settings of sdparm, hdparm, or the system itself when it comes to setting a time threshold before an external drive is put to sleep. If a platter drive is put to sleep after a very short idle time it is not only inconvenient, but it can also contribute to premature drive wear. My enclosure was being put to sleep after only three minutes of inactivity, and because of a limitation of the drive's enclosure I could not alter this annoying behavior. This script simply creates a hidden file at a predetermined write interval, to prevent the drive being put to sleep. This may be very important to you if you want a drive to always be accessible for unattended backups etc. The script will keep executing indefinitely in a loop to prevent the drive from sleeping, (unless the user desires to discontinue it).

The same thing can also be accomplished with a cron job or a service. However, I think this method is more flexible, as this method can be started/stopped at any time by various methods. For example, if you'd like this script to start automatically, you can add the script to autostart in the "KDE System Settings". You can also easily start/stop the script anytime via a bash alias or a desktop file. This script also does not require root privileges, so any user can execute or terminate the script.

Follow the steps below to create the script to keep an external USB hard drive from going to sleep.

*Note:
You may need to substitute your username for $USER in the following commands.

First, create the scripts storage location:

mkdir -p /home/$USER/.local/bin

You can locate the script in an alternate location if you desire, as long as it does not require root privileges to access.

*Note:
You may need to substitute your username for $USER in the following commands.

With a text editor, create the keep_alive script:

/home/$USER/.local/bin/keep_alive.sh

With the following contents:

#!/bin/bash
# cat /home/$USER/.local/bin/keep_alive.sh
# Script to prevent a USB hard drive from sleeping
time=$(date +%Y-%m-%[email protected]%H:%M)
if [ "$EUID" -eq 0 ]; then
  echo "This script cannot be executed as root - exiting script"
  sleep 2
  exit 1
fi
   /usr/bin/touch /run/media/$USER/10TB-WD-2/.stayawake &> /dev/null
if  [ -f /run/media/$USER/10TB-WD-2/.stayawake ]; then
    echo -e "Drive last awakened @ $time" > /run/media/$USER/10TB-WD-2/.stayawake
fi
sleep 150
exec $0

Save the script, and then make the script executable:

chmod +x /home/$USER/.local/bin/keep_alive.sh

It is advisable, (but not 100% necessary) to give the drive a permanent mount location via /etc/fstab using the nofail option. The drive's name and path in the script /run/media/$USER/10TB-WD-2 must obviously be modified to reflect your intended drive's name and path. The time frame to write the file is set via the sleep value in seconds (sleep 150). If you wish the time interval in seconds to be longer or shorter, then alter the value from 150. The file's name .stayawake , is preceded with a dot, so that the file is hidden on your intended drive target. You may alter the name of the file if you desire as well. The hidden .stayawake file is automatically created by the script, and it also writes a timestamp to the file specifying when it was last written.


If you ever wish to terminate the keep_alive script, run:

killall -9 keep_alive.sh

If you want the script to run automatically, add the script to KDE's autostart at login in System Settings , (and then restart). You can also use a bash alias to easily start or stop the script from the terminal whenever you choose.


I also wrote two handy desktop files (with customized icons) so the script can be started or stopped from your taskbar/dock/desktop if you'd prefer to use that easy GUI method.

With a text editor, create the hd_awake.desktop file on your users desktop to start the script:

/home/$USER/Desktop/hd_awake.desktop

With the following contents:

[Desktop Action New]
Exec=/bin/bash -c "/home/$USER/.local/bin/keep_alive.sh"
Name=Prevent Hard Drive Sleep

[Desktop Entry]
Actions=New;
Categories=System;Utility;
Comment[en_CA]=Prevent Hard Drive Sleep
Comment=Prevent Hard Drive Sleep
Exec=/bin/bash -c "/home/htpc/.local/bin/keep_alive.sh"
GenericName[en_CA]=Prevent Hard Drive Sleep
GenericName=Prevent Hard Drive Sleep
Icon=gsmartcontrol
MimeType=
Name[en_CA]=Prevent Hard Drive Sleep
Name=Prevent Hard Drive Sleep
Path=/home/htpc/.local/bin/keep_alive.sh
StartupNotify=true
Terminal=false
TryExec=/home/htpc/.local/bin/keep_alive.sh
NoDisplay=true
Path=
StartupNotify=false
Type=Application
Version=1.0
X-DBUS-ServiceName=
X-DBUS-StartupType=
X-KDE-SubstituteUID=false
X-KDE-Username=

Save the desktop file, and then make it executable:

chmod +x /home/$USER/Desktop/hd_awake.desktop

With a text editor, create the hd_sleep.desktop file on your users desktop to stop the script:

/home/$USER/Desktop/hd_sleep.desktop

With the following contents:

[Desktop Action New]
Exec=/bin/bash -c "killall -9 keep_alive.sh"
Name=Allow Hard  Drive Sleep

[Desktop Entry]
Actions=New;
Categories=System;Utility;
Comment[en_CA]=Allow Hard  Drive Sleep
Comment=Allow Hard  Drive Sleep
Exec=/bin/bash -c "killall -9 keep_alive.sh"
GenericName[en_CA]=Allow Hard  Drive Sleep
GenericName=Allow Hard  Drive Sleep
Icon=gnome-disks-state-standby-symbolic
MimeType=
Name[en_CA]=Allow Hard  Drive Sleep
Name=Allow Hard  Drive Sleep
StartupNotify=true
Terminal=false
NoDisplay=true
Path=
StartupNotify=false
Type=Application
Version=1.0
X-DBUS-ServiceName=
X-DBUS-StartupType=
X-KDE-SubstituteUID=false
X-KDE-Username=

Save the desktop file, and then make it executable.

chmod +x /home/$USER/Desktop/hd_sleep.desktop

After the desktop files are created you can then drag and drop them onto your taskbar/dock or your desktop for easy execution. Below is a screenshot of the "Awake" and "Sleep" buttons (created with the .desktop files) to the right of the Garuda logo (top left of taskbar).

If the icons I've chosen in the desktop file are not available on your system, then you will need to edit the Icon= line in the desktop file and choose an alternate icon.

Well that's about it. I hope my simple script helps someone.


Hopefully others will use this thread to share their scripting efforts as well. Feel free to add whatever scripts you've written so that other forum users can benefit and gain scripting knowledge.


16 Likes

This is a nice script to use instead of ever having to git add or git commit

───────┬─────────────────────────────────────────────────────────────────────────────────────
       │ File: commit
───────┼─────────────────────────────────────────────────────────────────────────────────────
   1   │ #!/bin/sh
   2   │ git add -u
   3   │ git diff HEAD
   4   │ git status -uno
   5   │ printf "Commit these files? [commit message, or blank to cancel]\n > " | lolcat
   6   │ read commitmsg
   7   │ [ -z "$commitmsg" ] && exit 1
   8   │ git commit -m "$commitmsg"
───────┴─────────────────────────────────────────────────────────────────────────────────────

This script lets you take multiple pdf files and outputs another pdf file appended. For example, if I have 1.pdf and 2.pdf then pdfcat 1.pdf 2.pdf will output (to stdout) another pdf with all the pages from 1.pdf then all the pages from 2.pdf. It's been useful a couple of times.

───────┬─────────────────────────────────────────────────────────────────────────────────────
       │ File: pdfcat
───────┼─────────────────────────────────────────────────────────────────────────────────────
   1   │ #!/bin/sh
   2   │ gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=- -f "[email protected]"
───────┴─────────────────────────────────────────────────────────────────────────────────────

This prints size information for all the files and directories in your working directory. I suppose I could update it to use exa, but its fine for now.

───────┬─────────────────────────────────────────────────────────────────────────────────────
       │ File: mydu
───────┼─────────────────────────────────────────────────────────────────────────────────────
   1   │ #!/bin/sh
   2   │ du -hd 1 --exclude=/proc* "[email protected]" 2>/dev/null | sort -h | lolcat
   3   │ ls -laFh "[email protected]" | sed '/^d/d;/^total/d' | awk '{print $5 "\t" $9}' | sort -hr
───────┴─────────────────────────────────────────────────────────────────────────────────────

I ilke to convert videos and audio to webms with ffmpeg to save space.

───────┬─────────────────────────────────────────────────────────────────────────────────────
       │ File: webmify
───────┼─────────────────────────────────────────────────────────────────────────────────────
   1   │ #!/bin/sh
   2   │ 
   3   │ name="$(echo $1 | cut -f1 -d'.')"
   4   │ ffmpeg -i "$1" "$name"'.webm' -loglevel warning -stats
───────┴─────────────────────────────────────────────────────────────────────────────────────

I have a few more at GitHub - magnus-ISU/usrlocalbin: Small scripts I've written that aren't worth putting in the AUR but these are the most useful and some of the ones there aren't really useful at all on KDE, I made them when I used vanilla arch with dmenu.

The coolest is sms I guess, which lets you send text messages through KDE connect on the command line.

7 Likes

Thought I'd play with my PS1 prompt today. I haven't played with it in a long time so I was pretty rusty.

Here's my simple PS1 prompt script:

if [[ ${EUID} == 0 ]] ; then
    PS1='\[\033[1;31m\]\[email protected]\h\[\033[0;33m\]: \w \[\033[2;31m \[\033[0;31m\]\n\$\[\033[0;33m\] '
#  ROOT PS1:   [email protected]$HOST  -  "RED"  ===  FULL PATH - "ORANGE"  ===  PROMPT  -  "RED"   ===  CURSOR  -  "RED"  ===  TERM FONT   - "ORANGE"
else
	PS1='\[\033[0;34m\]\[email protected]\h\[\033[0;34m\]: \[\033[2;32m\]$PWD \n\d \[\033[0;34m\]\@ \[\033[2;32m\]\n\$\[\033[0;00m\] '
#  USER PS1:   [email protected]$HOST  - "BLUE"  ===  FULL PATH WITHOUT  TILDE  -  "DIM GREEN"  ===  $DATE  -  "DEFAULT"  ===  $TIME  - "BLUE"  PROMPT  - "DIM GREEN"  ===  CURSOR  - "BRT GREEN"   ===  TERM FONT   -  "DEFAULT"
fi

# Set the cursor to blink red for root, green for user
printf  "\x1b[1 q"

Just in case anyone else likes playing with that kind of thing.

3 Likes

simple script to check for IOMMU groups

#!/bin/bash
shopt -s nullglob
for g in /sys/kernel/iommu_groups/*; do
    echo "IOMMU Group ${g##*/}:"
    for d in $g/devices/*; do
        echo -e "\t$(lspci -nns ${d##*/})"
    done;
done;

2 Likes

I've been exploring a freeze bug which can occur up to 2 weeks from launch. This script gives me the elapsed time from launch to freeze. Thanks to tbg for an example with exec $0 which is just cleaner looking than an infinite loop!

#!/bin/bash
# ./stats.sh
# First time only, show launch time and kernel version
if [ $# -eq 0 ]
then
  echo " "`date +%D" "%H:%M`" "`uname -svr`
fi

# update run time without scrolling the terminal screen 
printf " %s" `date +%D" "%H:%M` `uptime -p`
# erase any trailing characters leftover from last update
printf "       \r"
sleep 60
# relaunch script with one argument to suppress launch time echo
exec $0 1

My original script ran once and had to be manually run to update. This new script might affect my experiment if my freeze is related to inactivity... edit: it still froze.

Update: great idea until I guessed that the command line would grow each iteration. Maybe a simple loop is safer after all! The argument "1" is saved to $1 and not appended to $0.

2 Likes

I have a thing about notifiers - when I want to know something, I want to know it NOW. So - I have a couple of ways of avoiding notifiers on my setups. One of them is a conky setup that constantly displays counts of packages needing updates, along with a vertically scrolling list of what they are and what versions they will be.

The other is a couple of bash functions (which could be scripts easily enough) which I leave in my .bashrc file. One of them displays the names and versions of all packages (repo and AUR) - formatted and displayed, and the other just provides counts. If anyone else likes the info 'on demand' here they are:

Bash functions
upls() {
# create vars
NEWAUR=0

# count AUR updates
NEWAUR=`yay -Qua | wc -l`

# run checkupdates to create file in /tmp
		echo "Repo Packages" > /tmp/update-list
		echo "-------------" >> /tmp/update-list
checkupdates | column -t -N Name,Current,"->",New >> /tmp/update-list
if [[ ${NEWAUR} > 0 ]]; then
		echo " " >> /tmp/update-list
		echo "AUR Packages" >> /tmp/update-list
		echo "------------" >> /tmp/update-list
		yay -Qua | column -t -N Name,Current,"->",New >> /tmp/update-list
fi

# output RESULT
if [[ -s /tmp/update-list ]]; then
	cat /tmp/update-list
	rm /tmp/update-list
else
	echo "No pending updates..."
fi
}

# Function to count available updates

upcnt() {
# create vars
NEWPKG=0
NEWAUR=0

# count AUR and pacman updates
NEWAUR=`yay -Qua | wc -l`
NEWPKG=`checkupdates | wc -l`

# output RESULT
if [[ ${NEWPKG} == 0 && ${NEWAUR} == 0 ]]; then
	RESULT="System up-to-date"
else
	RESULT=$NEWPKG" Repo pkgs + "$NEWAUR" AUR pkgs need updating"
fi
echo $RESULT
}


#------------------------------------------------------------

This text will be hidden

No warranty on them having style points - but they DO work, and safely...

5 Likes

Some helping stuff here, just in case you find it interesting: https://www.kernel.org/doc/Documentation/usb/power-management.txt

EDIT: to make it smaller, and ... there is a USB autosuspend param in the kernel, maybe replacing the "usb disk keep alive" script ?
EDIT: nevermind.... my lack of basic reading skills made me make an ass out of myself! SORRY!

Gpu set max wattage usage ( i am on amd card ):

#!/bin/bash

DEVICE=$(find /sys/devices -name power1_cap)
WATT=50

if [ $# -eq 1 ]; then
    WATT=${1}
fi

echo ${WATT}000000 > $DEVICE

echo "auto" > /sys/class/drm/card0/device/power_dpm_force_performance_level

Because backups make life boring, snapper deleter!:

#!/bin/sh

for x in $(ls /etc/snapper/configs/) ;
do
    RANGE=$(snapper -c $x list | tail -n1 | cut -d" " -f1)
    if [ $RANGE -gt 0 ]; then
        echo Deleting snapshots in $x
        snapper -c $x delete 0-$RANGE
    else
        echo Nothing to delete in $x
    fi
done

Enable and execute the Magic Sysrq key combo via a bash alias for a graceful shutdown during a system hang.

Run this command to create an /etc/sysctl.d/99-sysctl.conf file:

echo kernel.sysrq=1 | sudo tee -a /etc/sysctl.d/99-sysctl.conf

Then run the following command to reload the sysctl config files without rebooting the computer:

su -c "sysctl --system"

Create a script to simulate pressing the magic sysrq key combination required to shutdown the computer gracefully during a system hang.

Create the script /usr/local/bin/magic.sh:

#!/bin/sh
# cat /usr/local/bin/magic.sh

for c in r s u o; do
  echo $c > /proc/sysrq-trigger
  sleep 2
done

Make the script executable:

sudo chmod +x /usr/local/bin/magic.sh

Then, add the following line to the ~/.bashrc file:

alias magic='sudo /usr/local/bin/magic.sh'

Then do:

source ~/.bashrc

SysRQ r s u o accomplishes a graceful shutdown without using the more usual r e i s u b magic sysrq key combination. Removed e and i since they kill processes, including the current shell and the rest of the script would also be killed.

r s u o =

r:

Turns off keyboard raw mode and sets it to XLATE.

s:

Will attempt to sync all mounted filesystems.

u:

Will attempt to remount all mounted filesystems read-only.

o:

Will shut your system off, (if configured).


This is a slightly modified version of a script from this source:

automate the magic sysrq


At the terminal type magic to easily execute a graceful shutdown during a system hang.


7 Likes

Not so much a full script, rather a simple one-liner I came up with, converted to be used as another alias:

alias balance='bash -c "sudo btrfs balance start -musage=60 -dusage=60 / & sudo watch -t -n5 btrfs balance status / &&  fg"'

I haven't exactly given this extensive testing as I just got it working. This alias will launch a 60% balance operation on / (root). The thing I find most annoying about a balance operation is not knowing how far along the process is to completion. Having to launch a separate command just to find out how things are progressing I find rather inconvenient and super annoying. I figured there must be a better way, so voila, my combined BTRFS balance and progress all in one alias. It actually works better as an alias than just launching the command itself. Give it a try if you're fond of using an alias for long commands there's no way you'll remember yourself.

YMMV, as more testing may be required.

7 Likes

Copying songs from an iPhone to linux might result in "numeric" file names, or so I've been told :wink: . That's a bit of a pain, since it's quite hard to identify a song called -234325432531.m4a. Luckily, there's SongRec (GitHub - marin-m/SongRec: An open-source Shazam client for Linux, written in Rust.) which uses Shazam. I've written a script that make use of SongRec's CLI features to rename a song (or a directory of songs) and create a decent directory structure based on the results (artist / album / [songName].m4a).

Two drawbacks to keep in mind:

  • "Best of" albums are still a pain in the butt, since Shazam will return the name of the original album, potentially resulting in quite a bunch of differnt album directories.
  • Shazam recognizes a lot, but not each and every song, so some manual labor may still be required.
#!/bin/bash
TIMEOUT=10

function renameSong() {
  fullSongPath=$1
  songFileBaseName=$(basename "${fullSongPath}")
  songFileExtension="${songFileBaseName##*.}"
  if [ "${songFileExtension}" == "m4a" ]; then
    if [ -f ${fullSongPath} ]; then
      echo "Trying to recognize song: ${fullSongPath}"
      songRecOutput=$(timeout ${TIMEOUT} songrec recognize "${fullSongPath}" --csv)
      if [ $? -eq 0 ]; then
    songRecOutput=$(echo -e "${songRecOutput}" | tail -1)
    parsedMetaData=$(parseMetaData ${songRecOutput})
    readarray -d "|" -t parsedMetaDataParts <<< "${parsedMetaData}"
        newSongPath="$(dirname ${fullSongPath})/${parsedMetaDataParts[0]}/${parsedMetaDataParts[1]}"
        mkdir -p "${newSongPath}"
    songName=$(echo ${parsedMetaDataParts[2]} | tr -d '\n')
        mv -f "${fullSongPath}" "${newSongPath}/${songName}.${songFileExtension}"
      else
        echo "Failed to recognize song within ${TIMEOUT} seconds. Skipping"
      fi
    else
      echo "Failed to find song: ${fullSongPath}"
    fi
    echo "-------------------------------------------------------"
  else
    echo "The following extension is not supported: ${songFileExtension}. Skipping"
  fi
}

function trim() {
  var="$*"
  var="${var#"${var%%[![:space:]]*}"}"
  var="${var%"${var##*[![:space:]]}"}"
  printf '%s' "${var}"
}

function fetchCsvPart() {
  text=$1
  csvPartNr=$2
  csvPart=$(echo "${text}" | awk -v idx="${csvPartNr}" '
    BEGIN {
      FPAT = "([^,]*)|(\"[^\"]+\")"
    }
    {
      printf("%s", $idx)
    }
  ')
  echo "${csvPart//\"/}"
}

function parseMetaData() {
  metaData=$1
  albumName=$(fetchCsvPart "${metaData}" "2")
  artistAndSongName=$(fetchCsvPart ${metaData} "1")
  readarray -d "-" -t artistAndSongNameParts <<< "${artistAndSongName}"
  artistName=$(trim "${artistAndSongNameParts[0]}")
  songName=$(trim "${artistAndSongNameParts[1]}")
  songName="${songName//[\/]/_}"
  if [ "${artistName}" == "" ]; then
    artistName="Unknown"
  fi
  if [ "${albumName}" == "" ]; then
    albumName="Unknown"
  fi
  echo "${artistName}|${albumName}|${songName}"
}

#main
IFS=$(echo -en "\n\b")
if [ $# -gt 0 ]; then
  fsArg=$(realpath $1)
  if [ -f "${fsArg}" ]; then
    renameSong "${fsArg}"
  elif [ -d ${fsArg} ]; then
    songFilePaths=$(find ${fsArg} -maxdepth 1 -mindepth 1 -type f -exec readlink -f {} \;)
    for songFilePath in ${songFilePaths}; do
      renameSong ${songFilePath}
    done
  else
    echo "The provided argument does not seem to be a valid file or directory: ${fsArg}"
  fi
else
  echo "Please provide either a song file name or a directory containing one or more song files"
fi

3 Likes

Nice script @guybrush, but now you just made my last post look like it was constructed by a 6 year old compared to yours. :rofl:

Although that one liner looks deceptively simple, it actually took a bit of figuring to get it to work. Trying to get both operations to launch concurrently, in acuallity wasn't that simple. If you try to launch both operations at the same time the progress command will not work because it doesn't detect a balance operation as taking place yet. If you try to launch the progress command sequentially it will not work because the balance has already completed. The trick was to background the balance command then display the progress command in the foreground. Super simple, once you figure out the trick (of course) . :wink:

2 Likes

I certainly didn't mean to make your script look any less. In fact, I do appreciate nifty tweaks, those are often the ones that make your head hurt (until you finally hit the jackpot :slight_smile: ). Isn't it fascinating how bash always seems to have more tricks up its sleeve?

3 Likes

Even if true (which it isn't) the 6 year old comment is kinda irrelevant in a world which has an 11 year old releasing a distro as lead dev :grin: (see Ubuntu Unity Remix - or whatever the currrent name is).

Of course, you could also point out how finely crafted an effective 1-liner must be to be useful!

5 Likes

I was recently messing up with video editing. I started using Davinci Resolve. (kdenlive missed a lot of features I required :slightly_frowning_face: ) But I came to know that .mp4 videos can't be edited (at least not in free version on GNU/Linux), So it must be converted to .mov. Here's a script, to convert all the .mp4 files to .mov, in current folder, and then delete older mp4 files. (You can remove rm "$filename" to prevent removing)

srcExt=mp4
destExt=mov


for filename in "."/*.$srcExt; do

        basePath=${filename%.*}
        baseName=${basePath##*/}

        ffmpeg -i "$filename" -c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a pcm_s16le -f mov "."/"$baseName"."$destExt"
        rm "$filename"

done

echo "Conversion from ${srcExt} to ${destExt} complete!"

To reverse .mov to .mp4 after editing, for reduced file size use

srcExt=mov
destExt=mp4


for filename in "."/*.$srcExt; do

        basePath=${filename%.*}
        baseName=${basePath##*/}

        ffmpeg -i "$filename" "$baseName"."$destExt"
        rm "$filename"

done

echo "Conversion from ${srcExt} to ${destExt} complete!"

And yeah I am 5 years old.
(At using Linux :grin: )

8 Likes

UltraBlack/srbs: Simple Rsync backup service - srbs - Wanna have a cup of GitTea?

A simple /home/$USER backup utility powered by rsync that I created earlier today

5 Likes

My interactive history clear script script

For Beginners : [Can be run through external program i.e konsole]


For PRO Users : [Minimal]
One click removal

#!/bin/bash

# Clear all the history from bash prompt @ .bash_history
    echo '' > /home/$USER/.bash_history

# Clear fish_history file
    echo '' > /home/$USER/.local/share/fish/fish_history

I'm just 1/2 year old linux user

1 Like

makeuapp() {
  if [ -f "/usr/share/applications/$1" ] ; then
    cp "/usr/share/applications/$1" "${HOME}/.local/share/applications"
    nvim "${HOME}/.local/share/applications/$1"
  else
    echo "'$1' is not a valid file"
  fi
}
_makeuapp_comp() {
    COMPREPLY=($(compgen -W "$(ls /usr/share/applications)" "${COMP_WORDS[1]}"))
}
complete -F _makeuapp_comp makeuapp

Very useful if you want to quickly edit a desktop file. Just remember to change the editor. I'm using neovim and didn't have the EDITOR variable set so I just hardcoded it

EDIT: You wanna put that into your .zshrc or .bashrc

getting a 404 here :confused:

I use fish, and now? :smiley:

1 Like

well rip xD

I don't use fish. It's syntax is aweful imo. All the shell sites use Bash syntax which makes it especially hard to get used to it's syntax