Handy scripts you'd like to share

Try your hand at bash scripting, it’s confusing at first, but it becomes almost addictive once you start building your knowledge base and get into it.

5 Likes

The forum CSS limits page width to 1110 pixels, of which 690 dedicated to the posts.

This is generally good, as it looks nice, wraps text at a reasonable width for readability, and gives a consistent look across devices.

However, when there are wide code blocks, I prefer to make use of the available screen width to read them inline rather than have to click to expand them and/or scroll sideways. I often miss something when I do that.

So, here’s my bookmarklet(*) to toggle “wide mode”:

(function(){const rs=document.querySelector(':root').style;if(rs.getPropertyValue('--d-max-width')==='100%'){rs.setProperty('--d-max-width','1110px');rs.setProperty('--topic-body-width','690px');}else{rs.setProperty('--d-max-width','100%');rs.setProperty('--topic-body-width','100%');}document.querySelector('#reply-control').style.setProperty('left','-10px');})()
Which one looks more readable?


The only issue I noticed is that sometimes it causes video embeds to jump back and forth between wide and narrow.
Also, it does not check the initial property values, it simply hardcodes the ones used on this forum. That said, it likely works on most other Discourse instances too.

(*) To use it, make a new bookmark on the toolbar and paste that line in the URL field, prefixed with javascript+: (I cannot write it in one piece or the forum won’t let me post)
When clicked it will toggle between 100% and the default values.

4 Likes

Nice one liner. A man after my own heart. :hugs:

1 Like

This is terrific! I just gave it a shot, it works great. I really like this a lot! :+1:

1 Like

Sometimes it happens, so: toggle-chaotic-r2

#!/bin/bash
cml='/etc/pacman.d/chaotic-mirrorlist' cdn='Server = https://cdn-mirror.chaotic.cx'
case "$1" in
  off) sed -Ei 's|^\s*'"$cdn"'|#&|' $cml ;;
   on) sed -Ei 's|^\s*#\s*('"$cdn"'.*)|\1|' $cml ;;
    *) echo "usage: sudo ${0##*/} on|off"
       grep -q '^\s*#\s*'"$cdn" $cml && echo 'R2 disabled' || echo 'R2 enabled'
esac

Move grep down one line to always report enabled/disabled after toggling.

Yes, final ;; is missing and bash is happy with it. Even shellcheck does not complain.

The gods would curse me if I don’t invoke sed often enough :slight_smile:

3 Likes

My friend told me he could paste YouTube links in his file manager to save the video on Windows 10, and I took that as a challenge :smile:. Made for dolphin context menus, but I’m sure you can use the helper script with your file manager of choice if it supports custom context menu entries. For now, it just grabs the best Audio/Video it can.

2 Likes

Just finished a script that allows you to automatically copy files off a usb drive!
It uses udev, and systemd templates, which are both quite interesting and useful!

https://chonkyrabbit.eu/git/random-tools.git/about/#autocopy

Here is the main part of the script. You’ll however still need the udev rule and service for this to work automagically

#!/usr/bin/bash

FROM=/mnt/autocopy
TO=/your/desired/path
TOUSR=1000
TOGRP=1000

KERNEL="/dev/$1"

/usr/bin/mkdir $FROM
/usr/bin/mount "$KERNEL" $FROM

if [[ -f $FROM/.dest ]]; then
	# ex. path/to/file_folder path/to/dest_folder

	while IFS= read -r LINE; do
		read -ra RULE <<< "$LINE"
		SOURCE=${RULE[0]}
		DEST=${RULE[1]}

		mkdir "/$TO/$DEST/"
		
		for FILE in $(/usr/bin/diff -qr "$FROM/$SOURCE" "$TO/$DEST" | /usr/bin/grep "$FROM" | /usr/bin/awk '{print $4}' | /usr/bin/grep -v ".dest"); do
			/usr/bin/install -o $TOUSR -g $TOGRP -p -m 755 "$FROM/$SOURCE/$FILE" "/$TO/$DEST/$FILE"
		done
	done <<< "$(/usr/bin/cat "$FROM/.dest")"
fi

/usr/bin/umount $FROM
3 Likes

I’m either too tired or too stupid to find the scripts, or my browser doesn’t open the links if there are any.
It can’t be that. :arrow_down:
https://chonkyrabbit.eu/git/random-tools.git/commit/?id=2b8e292744385b282fdff3c121c35b011f8bcc4b

click “tree” :wink:

edit: Ah yes, the stupid code formatter is broken again. Maybe I should disable that for now. Only the plain view works rn

(Oh I love the cgit docker container :upside_down_face: )

edit 2: fixed

1 Like

I did, before too, and I got

Ahh, on the right side I must click on plain.

I dont like new git confusions :slight_smile:

https://chonkyrabbit.eu/git/random-tools.git/plain/autocopy/autocopy.sh

as I said I just fixed the formatter. The fancy code view should now load. It’s a shell script, and if that for some reason doesn’t work it just prints nothing

1 Like

Gotcha, just trying to help so people can actually see it without whatever this whole process is.

3 Likes

Found old scripts in web, I wish I found them earlier :slight_smile:

# Check PC uptime
last -x | grep -E 'Jan  5' | grep -F 'system boot'
reboot   system boot  6.6.9-zen1-1-zen Fri Jan  5 23:35 - 03:23  (03:47)
reboot   system boot  6.6.9-zen1-1-zen Fri Jan  5 21:22 - 22:11  (00:48)
reboot   system boot  6.6.9-zen1-1-zen Fri Jan  5 16:40 - 21:03  (04:23)
reboot   system boot  6.6.9-zen1-1-zen Fri Jan  5 12:43 - 15:32  (02:49)
# Or just take the total time
last -x | grep -E 'Jan  5' | grep -F 'system boot' | sed 's/.*(\(.*\))/\1/' | awk -F: '{ h+=$1; m+=$2 } END { sum=h * 60 + m; printf("%02d:%02d\n", sum / 60, sum % 60) }'
11:47

Since last does not store infinitely, you would need a script for a timer that records daily (retroactively, script reports wrong daily value when the computer is on).


I am looking for the error in this script. I got 00:00 as result.

#!/bin/bash
# - From 2012 - 

# Ein paar feste Datumsangaben vorberechnen. "" ist notwendig,
# damit englische Bezeichner verwendet werden.
today=$(LANG=C date '+%a %b %_d')
yesterday=$(LANG=C date '+%a %b %_d' -d 'yesterday')
curyear=$(LANG=C date +%Y)
yesteryear=$(LANG=C date +%Y -d 'yesterday')

while read -r line
do
	# Zerlege die gelesene Zeile in $1, $2, ..., $9.
	set -- $line

	if [[ "$yesterday $yesteryear" =~ ^"$5 $6  "?"$7 $9"$ ]]
	then
		# Diese Zeile beschreibt eine Session, die gestern begann und
		# bis in den heutigen Tag andauert.

		# Shifts sind notwendig, weil Argumente größer als $9 nicht
		# adressierbar sind.
		shift; shift; shift; shift; shift

		# Zerlege die Uhrzeit, zu der der Rechner heute heruntergefahren
		# wurde, in Stunden und Minuten. Das ist dann logischerweise der
		# Anteil der Uptime dieser Session, der im heutigen Tag liegt.
		h=${9%%:*}
		ms=${9#*:}
		m=${ms%:*}

		# Addiere auf Gesamtsumme an Minuten. Wichtig ist explizite
		# Interpretation der Zahlen als Dezimalzahlen, sonst ergibt z.B.
		# "08" einen Fehler, weil es als Oktalzahl interpretiert wird.
		sum=$((sum + $((10#$h)) * 60 + $((10#$m)) ))
	else
		# Diese Zeile beschreibt eine Session, die am heutigen Tag
		# begann und auch endete (oder noch läuft).

		# Zerlege hier die Dauer, die "last" uns angibt.
		hm=${line##*(}
		hm=${hm%%)*}
		h=${hm%:*}
		m=${hm#*:}

		sum=$((sum + $((10#$h)) * 60 + $((10#$m)) ))
	fi
done < <(last -xF | grep -F 'system boot' | grep " - $today ........ $curyear")
#    └─┬┘└─┬┘  ││   │     └─────┬─────┘        └─────────────┬─────────────┘
#      │   │   ││   │           │                            │
#      │   │   ││   │           │                            └ Filtere nach der rechten
#      │   │   ││   │           │                              Spalte, zeige also nur
#      │   │   ││   │           │                              noch Einträge an, die
#      │   │   ││   │           │                              heute endeten. Sie
#      │   │   ││   │           │                              können jedoch gestern
#      │   │   ││   │           │                              begonnen haben. Die
#      │   │   ││   │           │                              vielen Punkte sind
#      │   │   ││   │           │                              Platzhalter für eine
#      │   │   ││   │           │                              Uhrzeit.
#      │   │   ││   │           │
#      │   │   ││   │           └ Uns interessieren nur Boots.
#      │   │   ││   │
#      │   │   ││   └ "fgrep" reicht hier, weil wir keinen regulären Ausdruck, sondern
#      │   │   ││     einen festen String testen.
#      │   │   ││
#      │   │   │└ Volle Uhrzeiten und Datumsangaben -- und nicht nur "May 5 22:20 -
#      │   │   │  03:25", denn da würde beim zweiten Zeitpunkt der Tag fehlen.
#      │   │   │
#      │   │   └ Auch shutdown-Zeiten und runlevel changes anzeigen.
#      │   │
#      │   └ "last" liest /var/log/wtmp aus. In dieser Datei sind Logins- und Logouts
#      │     gespeichert, aber auch weitere Informationen wie Boots. Siehe auch "man 5
#      │     wtmp".
#      │
#      └ Normalerweise würde man "last -xF ... | while read -r ..." schreiben, die
#        Ausgabe von "last" also in die "grep"s und letztendlich in "while" pipen. Das
#        können wir hier nicht machen, weil innerhalb der Schleife "sum" hochgerechnet
#        wird und wir diese Zahl auch nach der Schleife noch brauchen. "sum" ginge aber
#        bei einer normalen Pipe verloren, da die "while"-Schleife dann in einer
#        Subshell abgearbeitet werden würde. So aber läuft sie in der aktuellen Shell.

# Rechne die Minutensumme wieder in Stunden und Minuten um.
printf '%02d:%02d\n' $((sum / 60)) $((sum % 60))

# Next works but not correct :-D LANG=C is not needed
# last -x | head | grep -F 'system boot' | sed 's/.*(\(.*\))/\1/' | awk -F: '{ h+=$1; m+=$2 } END { sum=h * 60 + m; printf("%02d:%02d\n", sum / 60, sum % 60) }'

LOL, 00:00 with LANG=C or 1394:32, = 1394h/24 , not checked :smiley:

last -x |
         grep -F 'system boot' |
         # grep -F "$(LANG=C date '+%a %b %d')" |   # with lang result is 00:00
         sed 's/.*(\(.*\))/\1/' |
         awk -F: '{ h+=$1; m+=$2 } END { sum=h * 60 + m; printf("%02d:%02d\n", sum / 60, sum % 60) }'
1394:32

Have fun :slight_smile:

4 Likes

I love the way it’s commented, I take a bow and I take note.

:thinking: with LANG or with grep?

fish❯ LANG=C date '+%a %b %d'
dom gen 07
fish❯ date '+%a %b %d'
dom gen 07

and last says Sat Jan 6 20:54
but maybe it’s because I have mixed Italian/English locale vars
(well ok I copied the wrong one again)

3 Likes

Not my work but I also admire it very much.
I’m still looking for where I saved the link. :slight_smile:

Apparently they used a German shell here, back in 2012??? IDK

I have the same result as you but running it in script changes the output value. But I’ve tried so much now that I can’t figure it out anymore.
I’ve been trying to learn the secrets of programming for 40 years now, but unfortunately I’ve only been moderately successful in a few areas.
If a manual says something about foo/bar, my brain shuts down and I panic :smiley:

1 Like

LOL some seem written by an attorney.
There’s always the caveman approach: try and try harder until… whatever comes.

2 Likes

Don’t you go stealing my secret methods! I’ve never been much good at finessing things. I’ve always prefered the brute force, head-on, sledgehammer approach myself.

:hammer_and_pick:

2 Likes

If I can’t code in spaghetti I won’t can’t code any more. :sob:

I’m not kidding. I loved all my little batch files. Sometimes I’d even compile them from a .BAT to a .EXE.

That ended my foray.

EDIT. I had one called “income.exe” that prepared Income Approach analysis that I used in appraising for over 25 years. And it still worked.

2 Likes

I swap audio output between my speakers and headset all the time for gaming. The audio output systray tool included in Garuda Hyprland edition is nice, but I was getting annoyed at having to click through that thing so often. So I wrote a Python script that will swap between them for me, then bound that script to a key combo.

~/swapAudioOut.py

import re
import logging
import subprocess
from systemd.journal import JournalHandler

# full sink names captured from pactl
SpeakerSink = None
HeadsetSink = None
DefaultSink = None
NewSink = None

# audio sink name printed in notification
SpeakerNiceName = 'PreSonus AudioBox'
HeadsetNiceName = 'Logitech G535 Headset'
NewSinkNiceName = None

# regexes for audio sink names from pactl
SpeakerSinkRe = re.compile('(.*PreSonus_AudioBox.*)')
HeadsetSinkRe = re.compile('(.*Logitech_G535.*)')
DefaultSinkRe = re.compile('Default Sink: (.*)$')

log = logging.getLogger('swapAudioOut')
handler = JournalHandler()
handler.setFormatter(logging.Formatter('[%(levelname)s] %(message)s'))
log.addHandler(handler)

def sendErrorNotification():
	cmd = f'notify-send -t 5000 \"Audio swap failure!\" \"Check journal for details.\"'
	subprocess.Popen(cmd, shell=True)

def logSinks():
	log.error(f'Speaker sink: {str(SpeakerSink)}')
	log.error(f'Headset sink: {str(HeadsetSink)}')

pactlOutput = subprocess.Popen('pactl list short sinks', shell=True, stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
splitOutput = pactlOutput.split('\t')

for sink in splitOutput:
	SpeakerMatch = SpeakerSinkRe.match(sink)
	if SpeakerMatch:
		SpeakerSink = SpeakerMatch.group()

	HeadsetMatch = HeadsetSinkRe.match(sink)
	if HeadsetMatch:
		HeadsetSink = HeadsetMatch.group()

if not SpeakerSink or not HeadsetSink:
	log.error('One or more sinks not found.')
	logSinks()
	sendErrorNotification()
	sys.exit()

pactlOutput = subprocess.Popen('pactl info', shell=True, stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
splitOutput = pactlOutput.split('\n')

for line in splitOutput:
	DefaultMatch = DefaultSinkRe.match(line)
	if DefaultMatch:
		DefaultSink = DefaultMatch.group(1)

if DefaultSink is None:
	log.error('Default sink not found.')
	sendErrorNotification()
	sys.exit()

if DefaultSink == HeadsetSink:
	NewSink = SpeakerSink
	NewSinkNiceName = SpeakerNiceName
elif DefaultSink == SpeakerSink:
	NewSink = HeadsetSink
	NewSinkNiceName = HeadsetNiceName

if NewSink:
	subprocess.Popen(f'pactl set-default-sink {NewSink}', shell=True)
	cmd = f'notify-send -t 5000 \"Audio output changed\" \"{NewSinkNiceName}\"'
	subprocess.Popen(cmd, shell=True)
else:
	logSinks()
	log.error(f'New sink: {str(NewSink)}')
	sendErrorNotification()

The names of my headset and speaker audio sinks (PreSonus AudioBox and Logitech G535) are hard coded in here. I’ve seen some solutions to this that are more generic and will flip through all your available sinks, but I just wanted the script to toggle between these two. You can change the strings and regexes to match the sinks on your machine - get them with “pactl list short sinks”.

Then in my ~/.config/hypr/hyprland.conf,

bind = $mainMod, F5, exec, python3 ~/swapAudioOut.py
5 Likes

Small fish shell script to show battery status for Razer wireless keyboard which I use as plugin for the Waybar. Hope it will help someone

#!/bin/fish

set rz_critical 10

set rz_charge $(razer-cli --battery print | grep charge | awk '{print $2}')
set rz_charging $(razer-cli --battery print | grep charging | awk '{print $2}')

# checking battery current charge for critical level
if test $rz_charge -le $rz_critical; and test $rz_charging = 'False'
    set rz_status 'critical'
else if test $rz_charging = 'True'
    set rz_status 'Charging'
else
    set rz_status 'Discharging'
end

echo "{\"class\": \"$rz_status\", \"percentage\": $rz_charge}"

Razer BW keyboard battery status - Git repo

and with the same purpose script for the Steel Series Rival mouse

#!/bin/fish

set ss_critical 10
set ss_report $(rivalcfg --battery-level)

set ss_charge $(echo $ss_report | sed 's/^[^]]*]//' | awk '{print $1}')
set ss_status $(echo $ss_report | awk '{print $1}')

# checking battery current charge for critical level
if test $ss_charge -le $ss_critical; and test $ss_status = 'Discharging'
   set ss_status 'critical'
end

echo "{\"class\": \"$ss_status\", \"percentage\": $ss_charge}"

Rival 650 battery status - Git repo

5 Likes