Hi GNUser
OK, I'm back.
Here's the new cut:
#!/bin/sh
# Script to reset a USB device written by Richard A. Rost September 4,2024
# This script attempts to replicate the behavior of unplugging/replugging
# a USB device by unbinding/binding the devices driver.
# *************************************************************************** #
#
# Default behavior for Debug and Reset can be changed here.
# -d command line switch turns on debugging messages.
Debug="test"
# Uncomment the next line to turn on debugging messages.
#Debug="echo"
# -r command line switch requests resetting the device.
Reset="No"
# Uncomment the next line to request resetting the device.
#Reset="Yes"
# *************************************************************************** #
# --------------------------------- Aliases --------------------------------- #
alias cp='busybox cp'
alias cut='busybox cut'
alias mkdir='busybox mkdir -p'
alias rm='busybox rm -rf'
alias tr='busybox tr'
# ------------------------------- End Aliases ------------------------------- #
# -------------------------------- Constants -------------------------------- #
# Current version.
VERSION="Version 0.1"
# Maximum number of command line parameters we accept.
MAXPARAMS=2
# Number of command line parameters passed in.
PARAMCOUNT=$#
# List of parameters passed in.
PARAMLIST=$@
# Needed for embedding a newline character in some commands.
NL="
"
# These are the properties we'll be searching for in WalkTheSysPath().
SEARCHTERMS="SUBSYSTEM= DRIVER="
# ------------------------------ End Constants ------------------------------ #
# -------------------------------- Variables -------------------------------- #
# Set if user passes in /dev/, such as  /dev/sdg , /dev/sdg1 , or  /dev/ttyUSB0.
DEV=""
# Set if user passes in VID:PID. VID and PID must each be 4 digits separated by
# a colon. VID and PID are hex and may contain letters. Both uppercase and
# lowercase are acceptable.
VIDPID=""
# VID and PID separated with colon removed.
VID=""
PID=""
# Variable used for temporary lists.
TMPLIST=""
# The path we traverse from end to beginning in search for its driver.
DEVPATH=""
# Working copy of DEVPATH used by WalkTheSysPath().
SEARCHPATH=""
# The text after the last slash in DEVPATH. Echoed into unbind/bind to
# identify which USB device to effect.
ENDPATH=""
# These are the properties we are scanning for. We want to find:
# [ SUBSYSTEM == "usb" ] && [ DRIVER != "" ]
SUBSYSTEM=""
DRIVER=""
# Driver directory.
BINDPATH=""
# That directory should contain the unbind and bind files.
BINDFILE=""
UNBINDFILE=""
# ------------------------------ End Variables ------------------------------ #
# -------------------------------- Functions -------------------------------- #
# --------------------------------------------------------------------------- #
ParseCommand()
{
#	[ $PARAMCOUNT -gt $MAXPARAMS ] && Usage "To many parameters entered."
	for ITEM in $PARAMLIST
	do
		case $ITEM in
			-h)	Usage ;;
			-help)	Usage ;;
			--help)	Usage ;;
			-d)	# Throw away -d to avoid  Invalid option  error. It's handled in Main.
				;;
			-r)	# Request device reset.
				Reset="Yes"
				;;
			"/dev/"?*)
				# Skip if DEV already defined.
				[ -n "$DEV" ] && continue
				# Skip if VIDPID already defined.
				[ -n "$VIDPID" ] && continue
				DEV="$ITEM"
				# The results are saved along with string lengths for sorting later.
				for DEVPATH in $(udevadm trigger -v -n -t devices -p DEVNAME="$DEV")
				do
					TMPLIST=$TMPLIST$(printf "%04d %s\n" ${#DEVPATH} "$DEVPATH")$NL					
				done
				"$Debug" "TMPLIST=$TMPLIST"
				;;
			[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])
				# Skip if DEV already defined.
				[ -n "$DEV" ] && continue
				# Skip if VIDPID already defined.
				[ -n "$VIDPID" ] && continue
				# Covert uppercase characters to lowercase.
				VIDPID="$(echo "$ITEM" | busybox tr '[A-Z]' '[a-z]')"
				# Separate VID from PID and remove colon.
				VID="$(echo "$VIDPID" | cut -c 1-4)"
				PID="$(echo "$VIDPID" | cut -c 6-9)"
				"$Debug" "VID=$VID    PID=$PID"
				# This won't work because it searches for "ID_VENDOR_ID=0644 OR ID_MODEL_ID=0200"
				# udevadm trigger -v -n -t devices -p ID_VENDOR_ID=0644 -p ID_MODEL_ID=0200
				udevadm trigger -v -n -t devices -p ID_VENDOR_ID="$VID" | sort > VIDs.txt
				udevadm trigger -v -n -t devices -p ID_MODEL_ID="$PID" | sort > PIDs.txt
				# Since we had to search for VID and PID separately, we check which
				# results are common to both lists for an exact match. The results
				# are saved along with string lengths for sorting later.
				for DEVPATH in $(comm -12 VIDs.txt PIDs.txt)
				do
					TMPLIST=$TMPLIST$(printf "%04d %s\n" ${#DEVPATH} "$DEVPATH")$NL					
				done
				# Clean up the files we created.
				rm VIDs.txt PIDs.txt
				"$Debug" "TMPLIST=$TMPLIST"
				;;
			*)
				echo "Invalid option."
				exit 1
				;;
		esac
	done
}
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
ParseList()
{
	# Parse list of DEVPATHs returned by udevadm and save the longest line.
	# Sort our list, longest lines first, remove string length, and
	# save the first entry (longest string).
	DEVPATH="$(echo "$TMPLIST" | sort -r | cut -c 6- | head -n 1)"
	"$Debug" "DEVPATH=$DEVPATH"
	TMPLIST=""
	# Make sure we got result.
	[ -z "$DEVPATH" ] && echo "No DEVPATH found" && exit 1
	# Make sure it's USB device.
	[ ! $(echo "$DEVPATH" | grep -E '/usb[0-9]') ] && echo "Not a USB device" && exit 1
}
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
SetupBindPaths()
{
	# Find the drivers directory.
	BINDPATH="$(find /sys/bus/usb/drivers/ -type d -name "$DRIVER")"
	[ -z "$BINDPATH" ] && echo "No BINDPATH found" && exit 1
	# That directory should contain the unbind and bind files.
	BINDFILE="$BINDPATH""/bind"
	UNBINDFILE="$BINDPATH""/unbind"
	"$Debug" "BINDPATH=$BINDPATH"
	"$Debug" "BINDFILE=$BINDFILE"
	"$Debug" "UNBINDFILE=$UNBINDFILE"
	# Make sure BINDPATH contains correct ENDPATH link.
	[ "$(realpath "$BINDPATH/$ENDPATH")" != "$SEARCHPATH" ] && echo "Bad BINDPATH" && exit 1
}
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
Usage()
{
	[ "$1" != "" ] && echo "$1" && echo
	echo "
$0 $VERSION
This script attempts to reset a USB device without an unplug/replug
sequence using a supplied  /dev/device  OR  VID:PID. The device will
only be reset if the  -r  switch is included.
Usage:
	$0 /dev/device | VID:PID [ -d -r ]
	/dev/device	Must point to a connected USB device.
	VID:PID		Two 4 digit IDs separated by a colon.
			Since IDs are hex, they may contain
			letters. Upper and/or lower case are OK.
	-d		Turn on debugging messages.
	-r		Request device reset.
Command line switches must be given separately, ie -d -r, not -dr.
"
	exit
}
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
WalkTheSysPath()
{
	# This walks up the DEVPATH by shortening it one entry at a time in an
	# effort to located its driver.
	"$Debug" "$NL""WalkTheSysPath(): Searching for SUBSYSTEM and DRIVER."
	# SEARCHPATH is our working copy of DEVPATH.
	SEARCHPATH="$DEVPATH"
	# Copy the text after the last slash in SEARCHPATH.
	ENDPATH="${SEARCHPATH##*/}"
	# When ENDPATH begins with  usbN*  where N is a digit, we've hit the USB border.
	while [ ! $(echo "$ENDPATH" | grep -E '^usb[0-9]') ]
	do
		"$Debug" "$NL""SEARCHPATH=$SEARCHPATH"
		INFO="$(udevadm info -q property -p "$SEARCHPATH" 2> /dev/null)"
		for ST in $SEARCHTERMS
		do
			# Search for our search term (i.e. SUBSYSTEM=).
			T="$(echo "$INFO" | grep -E "^$ST")"
			# Remove our search term (i.e. SUBSYSTEM=) from the result.
			T="${T/$ST/}"
			case $ST in
			"SUBSYSTEM=")
				# If the result matched our search term, save it.
				SUBSYSTEM="$T"
				"$Debug" "$ST$T"
				;;
			"DRIVER=")
				DRIVER="$T"
				"$Debug" "$ST$T"
				;;
			esac
		done
		if [ "$SUBSYSTEM" == "usb" ] && [ "$DRIVER" != "" ]
		then
			# We found what we were searching for.
			"$Debug" "$NL""WalkTheSysPath(): Search complete."
			"$Debug" "SEARCHPATH=$SEARCHPATH"
			"$Debug" "ENDPATH=$ENDPATH    SUBSYSTEM=$SUBSYSTEM    DRIVER=$DRIVER"
			return
		fi
		# Clear the properties for the next pass.
		SUBSYSTEM=""
		DRIVER=""
		# Shorten the path and update ENDPATH for the next pass
		SEARCHPATH="${SEARCHPATH%/*}"
		ENDPATH="${SEARCHPATH##*/}"
	done
	"$Debug" "$NL""WalkTheSysPath(): Search failed."
}
# --------------------------------------------------------------------------- #
# ------------------------------ End Functions ------------------------------ #
# ---------------------------------- Main ----------------------------------- #
# Program starts here.
[ $PARAMCOUNT -eq 0 ] && Usage
for ITEM in $PARAMLIST
do
	# We want this to happen before any debug messages are encountered.
	# Turn on debugging messages if option  -d  is specified.
	[ "$ITEM" == "-d" ] && Debug="echo" && break
done
ParseCommand
ParseList
WalkTheSysPath
SetupBindPaths
# See if device reset (-r) was requested.
if [ "$Reset" == "Yes" ]
then
	if [ -e "$UNBINDFILE" ]
	then
		# Remove device.
		echo "$ENDPATH" | sudo tee "$UNBINDFILE" 1> /dev/null
	fi
	"$Debug" "Sleep ..."
	sleep 3
	"$Debug" "... Wake up"
	if [ -e "$BINDFILE" ]
	then
		# Add device back.
		echo "$ENDPATH" | sudo tee "$BINDFILE" 1> /dev/null
	fi
fi
exit
Help is included:
tc@E310:~$ ./Scripting/USB_Reset/USBreset.sh -h
./Scripting/USB_Reset/USBreset.sh Version 0.1
This script attempts to reset a USB device without an unplug/replug
sequence using a supplied  /dev/device  OR  VID:PID. The device will
only be reset if the  -r  switch is included.
Usage:
        ./Scripting/USB_Reset/USBreset.sh /dev/device | VID:PID [ -d -r ]
        /dev/device     Must point to a connected USB device.
        VID:PID         Two 4 digit IDs separated by a colon.
                        Since IDs are hex, they may contain
                        letters. Upper and/or lower case are OK.
        -d              Turn on debugging messages.
        -r              Request device reset.
Command line switches must be given separately, ie -d -r, not -dr.
The default behavior is  debugging messages  and  device reset  are off.
Use -d and -r to turn them on. There are settings at the beginning of the
script if you want to have them default to on. There are no - switches to
turn them off.
This is what it looks like resetting a thumb drive with debugging messages on:
tc@E310:~$ ./Scripting/USB_Reset/USBreset.sh 8644:8003 -d -r > USBtest.txt
VID=8644    PID=8003
TMPLIST=0045 /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5
0089 /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg
0094 /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg/sdg1
DEVPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg/sdg1
WalkTheSysPath(): Searching for SUBSYSTEM and DRIVER.
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg/sdg1
SUBSYSTEM=block
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg
SUBSYSTEM=block
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block
SUBSYSTEM=
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0
SUBSYSTEM=scsi
DRIVER=sd
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0
SUBSYSTEM=scsi
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7
SUBSYSTEM=scsi
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0
SUBSYSTEM=usb
DRIVER=usb-storage
WalkTheSysPath(): Search complete.
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0
ENDPATH=1-5:1.0    SUBSYSTEM=usb    DRIVER=usb-storage
BINDPATH=/sys/bus/usb/drivers/usb-storage
BINDFILE=/sys/bus/usb/drivers/usb-storage/bind
UNBINDFILE=/sys/bus/usb/drivers/usb-storage/unbind
Sleep ...
... Wake upTMPLIST  shows the results udevadm returned for the VID:PID which I
prefixed with their string lengths so I could grab the longest one.
DEVPATH contains the chosen string.
WalkTheSysPath() asks udevadm for the properties of SEARCHPATH.
It's looking for a match on SUBSYSTEM == "usb" && DRIVER != ""
If it doesn't match, go up one level on SEARCHPATH and try again
This continues until a match is found, or fails if it reaches .../usb[0-9]
Once found, the bind path, bind files, and what to echo into them
are determined.
The new (and hopefully working) version is attached.