bashshellscriptingstdoutio-redirection

Redirect stderr/-out of running application to my own bash script


I am running an application called "hd-idle". It is spinning down disks after a specific time of inactivity. The output looks like this:

user@linux:~$ sudo /usr/sbin/hd-idle -i 10800
symlinkPolicy=0, defaultIdle=10800, defaultCommand=scsi, defaultPowerCondition=0, debug=false, logFile=, devices=
sda spindown
sdd spindown
sde spindown
sda spinup
sdd spinup
sdd spindown
[...]

I want to save this output to a logfile (while the application in running), add timestamps and change sd[a-z] to corresponding model/serial of the hard drive.

I wrote a small bash script that does what I want:

user@linux:~$ cat hd_idle_logger.sh
#!/bin/bash

DATUM=$(date '+%Y-%m-%d %H:%M:%S')
INPUT=$(cat)

REGEX='(sd[a-z])\s(spin(down|up))'

[[ $INPUT =~ $REGEX ]]

if [ -n ${BASH_REMATCH[1]} ]
then
    MODEL=$(lsblk /dev/${BASH_REMATCH[1]} -n -o MODEL)
    SERIAL=$(lsblk /dev/${BASH_REMATCH[1]} -n -o SERIAL)
fi

echo -e "$DATUM\t${MODEL}_$SERIAL (${BASH_REMATCH[1]})\t${BASH_REMATCH[2]}" >> /home/linux/hd_idle_logger.log


I can verify that it works:

user@linux:~$ echo "sdd spindown" |& ./hd_idle_logger.sh

user@linux:~$ cat hd_idle_logger.log
2023-02-12 12:14:54     WDC_WD120EMAZ-10BLFA6_1PAEL2ES (sdd)    spindown

But running the application and passing the output to my script doesn't work, the logfile doesn't produce any content and I don't see the output on console anymore:

user@linux:~$ sudo /usr/sbin/hd-idle -i 10800 |& /home/user/hd_idle_logger.sh

So what I am doing wrong?


Solution

  • As long as hd-idle is running, your script will be stuck at INPUT=$(cat). Because $(cat) has to capture ALL output, it can online terminate once hd-idle terminated.

    You need a script/program that processes hd-idle's output on the fly; e.g. line by line, while hd-idle is still running. You could do this with a while read loop:

    #! /bin/bash
    regex='(sd[a-z])\s(spin(down|up))'
    while IFS= read -r line; do
      [[ $line =~ $regex ]] || continue
      model=$(lsblk /dev/"${BASH_REMATCH[1]}" -n -o MODEL)
      serial=$(lsblk /dev/"${BASH_REMATCH[1]}" -n -o SERIAL)
      printf '%(%Y-%m-%d %H:%M:%S)T\t%s_%s (%s)\t%s\n' \
        "$model" "$serial" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
    done >> /home/linux/hd_idle_logger.log
    

    However, it would be more efficient to switch to utils like sed or awk and pre-compute the list of serial numbers or look for the required information in the /sys/block file system, so that you don't have to execute lsblk for each line.