1

I came across this nice command dmesg -w which will output the device info when new device is inserted/removed.

Is there a way to use this command to pause the script untill user inserts any kind of USB or SSD card? I was thinking of redirecting the output and using read somehow...

I normaly use the two command below to get device name from dmesg:

SSD_DMESG=$(dmesg | tail -n1 | grep -o 'sd[[:lower:]]')
SSD=/dev/$SSD_DMESG
71GA
  • 1,132
  • 6
  • 36
  • 69
  • 3
    What OS are you using that supports a -w flag to dmesg? – Nick Russo Oct 03 '14 at 23:43
  • 1
    On Linux, at least, there are better ways to trigger events on device insertion and removal -- you can have udev or whichever equivalent your OS ships trigger only on device insertion and removal, rather than needing to constantly monitor. – Charles Duffy Oct 04 '14 at 00:30
  • @Nick Russo I am using ArchLinux. @Charles Duffy I have no `udev`, but I have `udevadm`. – 71GA Oct 04 '14 at 01:10
  • 1
    @71GA, `udev` isn't a command, it's a daemon which can be configured. If you have udevadm, you have udev, and can craft an appropriate config file (typically under `/etc/udev/rules.d`) which will tell udev to start your script whenever a plug or unplug event happens. See http://www.reactivated.net/writing_udev_rules.html – Charles Duffy Oct 04 '14 at 04:34

4 Answers4

3

This script counts how many sd[[:lower:]] lines are currently in the dmesg queue. It then waits for more lines to appear. When they do, it prints them:

#!/bin/sh
n=$(dmesg | grep  'sd[[:lower:]]' | wc -l)
while [ "$n" -eq "$(dmesg | grep  'sd[[:lower:]]' | wc -l)" ]
do
        sleep 0.1s
done
sleep 0.1s
dmesg | grep  'sd[[:lower:]]' | tail -n+$n

wc -l is used to provide a line-count. tail -n+$n is used to remove the prior existing lines. The purpose of the last sleep is to allow time for the kernel to finish processing the device.

The above is verbose. If you just want the sd? symbol and no other information, try:

#!/bin/sh
n=$(dmesg | grep -o  'sd[[:lower:]]' | wc -l)
while [ "$n" -eq "$(dmesg | grep -o  'sd[[:lower:]]' | wc -l)" ]
do
    sleep 0.1s
done
sleep 0.1s
dmesg | grep -o 'sd[[:lower:]]' | tail -n+$n | sort -u
John1024
  • 109,961
  • 14
  • 137
  • 171
2

udevadm's monitor mode will provide a feed of uevents as they are generated:

grep --line-buffered -o -m1 'sd[a-z] ' \
   <(stdbuf -i0 -o0 udevadm monitor --kernel --subsystem-match=block)

There's a few things here worth noting:

  • stdbuf is used to work around udevadm's output buffering behavior.
  • --line-buffered tells grep to output each line as it comes in instead of potentially holding it in a buffer.
  • BASH process substitution (The <()) is used to turn the output of udevadm in to a /dev/fd/* symlink that grep can open as a file and read from. If a pipeline was used instead (udevadm | grep), the udevadm process could end up hanging around even after the grep process has exited, which in turn would hold up the parent process.
  • Remove the -m argument for a continuous stream.
Community
  • 1
  • 1
iscfrc
  • 421
  • 2
  • 6
1

I've just recently tackled this same problem. I'm trying to monitor dmesg so that I can discover USB MIDI devices as they are connected.

First, let's not use dmesg -w, since doing so will also display events that have occurred recently in the past. Instead, let's tail /var/log/messages in refresh mode, specifying 0 lines to be displayed initially:

tail -f -n 0 '/var/log/messages'

Now that we know what command we want to monitor, how can we await a new dmesg entry that meets our criteria, and then terminate our polling? Fortunately, bash redirection makes it easy to put a task into the background, and subsequently access its STDOUT and STDIN:

exec 3< <( tail -f -n 0 '/var/log/messages' )

Here, we've redirected the output of our tail operation into file descriptor 3, which we can unset as follows once we've encountered our criteria:

exec 3>&-

Note that unsetting the file descriptor also terminates (via SIGPIPE) the command which was bound to it, so in this case, we don't have to worry about cleaning up the tail operation.

Now that we know how to bind and unbind our backgrounded command to a file descriptor, we can execute a while loop that will read and process each line of text provided via file descriptor 3:

exec 3< <( tail -f -n 0 '/var/log/messages' )

local line

while IFS='' read line
do
  :
done <&3

The read command will patiently await new lines of text to appear on file descriptor 3, and the while command will loop until the read command says that there are no more lines of text to read. read would only do so if file descriptor 3 is closed, and since that descriptor is bound to tail -f, which never closes its output voluntarily, the while loop will not terminate, and we must thus manually break the loop.

Now we can add a statement that evaluates $line in some manner, and breaks the loop if some particular criteria matches. Here, we can use the success status of grep -o to determine if we've found what we're looking for, and if so, do something appropriate, including breaking the loop and then closing file descriptor 3:

exec 3< <( tail -f -n 0 '/var/log/messages' )

local line

while IFS='' read line
do
  echo "${line}" | grep -o 'sd[[:lower:]]' && {

    echo "YAY!  ${line} matched."
    break
  }
done <&3

exec 3>&-

If you want to implement a solution that doesn't use bash file redirection in this manner, please refer to several useful alternatives documented at Bash: Capture output of command run in background.

Community
  • 1
  • 1
Dejay Clayton
  • 3,710
  • 2
  • 29
  • 20
0

Here's my two cents. Use timeout, journalctl, stdbuf, and grep; thusly:

if timeout $max_to stdbuf -oL -eL journalctl -b -f | grep -l  --line-buffered "."; then
    # grep found what you were looking for.
else
    # TIMEOUT
fi 

Let's start with grep. The -l option with cause grep to return as soon as a match is found. --line-buffered eliminates the buffering problem with grep only. '.' will match anything.

journalctl -b -f tell journalctl to print messages starting from the boot time and continue to output messages until terminated. This means that you won't have to worry about a race condition where the message that you're looking for happened before your call to dmesg.

stdbuf will eliminate the buffering issues with journalctrl.

And now for the coup de gras, the timeout command will terminate everything if the command following it doesn't terminate before the given number of seconds; beautiful!

shrewmouse
  • 5,338
  • 3
  • 38
  • 43