0

I have a bash script to check my removable devices in the system. This script is called by Conky to dynamically show/hide said devices and their used and free space.

The script seems to be working fine:

  • When inserting a new device, it shows up in Conky. Inserted

  • And it disappears when it is removed. Removed

However, after removing a device, conky will complain with the following message until the device is plugged back again:

conky: statfs64 '/media/acs/TOSHIBA': No such file or directory

Is there a way to make conky stop complaining about the removed devices?


Conky configuration (relevant parts only):

conky.config = {
    alignment = 'top_right',
    background = false,
    border_width = 1,
    cpu_avg_samples = 2,
    default_color = 'white',
    default_outline_color = 'white',
    default_shade_color = 'white',
    double_buffer = true,
    draw_borders = false,
    draw_graph_borders = true,
    draw_outline = false,
    draw_shades = false,
    font = 'DejaVu Sans Mono:size=12',
    gap_x = 10,
    gap_y = 60,
    minimum_height = 5,
    minimum_width = 5,
    net_avg_samples = 2,
    no_buffers = true,
    out_to_console = false,
    out_to_stderr = false,
    extra_newline = false,
    own_window = true,
    own_window_class = 'Conky',
    own_window_transparent = false,
    own_window_type = 'desktop',
    own_window_argb_visual = true,
    own_window_argb_value = 120,
    own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
    stippled_borders = 0,
    update_interval = 1.5,
    uppercase = false,
    use_xft = true,
    use_spacer = 'none',
    show_graph_scale = false,
    show_graph_range = false,
    template0 = [[${if_match "${hddtemp \1}" <= "45.0"}${color green}${hddtemp /dev/sda}º${color}${else}${color red}${hddtemp /dev/sda}º${color}${endif}]],
    template1 = [[${if_match "${execi \1 sensors | grep 'Core \2' | cut -c16-20}" <= "60.0"}${color green}${execi \1 sensors | grep 'Core \2' | cut -c16-21}${color}${else}${color red}${execi \1 sensors | grep 'Core \2' | cut -c15-21}${color}${endif}]],
    template2 = [[${font FontAwesome}DISK${font}${voffset -2} \1 ${alignr 30} ${fs_used \1}/${fs_size \1} (${fs_used_perc \1}%)]],
    template3 = [[${font FontAwesome}${color orange}MEDIA${color}${font}${voffset -2} \1 ${alignr 30} ${fs_used \2}/${fs_size \2} (${fs_used_perc \2}%)]]
}

conky.text = [[
    ${color grey}Uptime:${color} $uptime
    ${color grey}Avg. CPU Frequency (GHz):${color} $freq_g
    ${color grey}Avg. CPU Usage:${color} $cpu% ${cpubar 4}
    ${color grey}RAM Usage:${color} $mem/$memmax - $memperc% ${membar 4}
    ${color grey}Swap Usage:${color} $swap/$swapmax - $swapperc% ${swapbar 4}
    ${hr}
    ${font FontAwesome}DISK ${font} /dev/sda (${template0 /dev/sda})
    ${goto 20}${diskiograph_read /dev/sda 30,178 06E9F8 2104FA}${goto 202}${diskiograph_write /dev/sda 30,175 FFFF00 DD3A21}
    ${font FontAwesome}${goto 20}DISK${font} ${diskio_read /dev/sda}${font FontAwesome}${goto 202}DISK${font} ${diskio_write /dev/sda}
    ${hr}
    ${color grey}File systems:${color}
     ${template2 /}
     ${template2 /home}
     ${execpi 1.5 ./conkscript/find-removable-devices.sh}
    $hr
    ${color grey}CPUs:${color}
    ${goto 20}CPU1: ${cpu cpu1}% ${cpubar 7,50 cpu1} - ${freq_g 1} GHz - ${template1 60 0}
    ${goto 20}CPU2: ${cpu cpu2}% ${cpubar 7,50 cpu2} - ${freq_g 1} GHz - ${template1 60 1}
    $hr
    ${color grey}Processes:${color} $processes  ${color grey}Running:${color} $running_processes
    ${color grey}Name              PID   CPU%   MEM%
    ${color lightgrey} ${top name 1} ${top pid 1} ${top cpu 1} ${top mem 1}
    ${color lightgrey} ${top name 2} ${top pid 2} ${top cpu 2} ${top mem 2}
    ${color lightgrey} ${top name 3} ${top pid 3} ${top cpu 3} ${top mem 3}
    ${color lightgrey} ${top name 4} ${top pid 4} ${top cpu 4} ${top mem 4}

]]

Script:

#!/bin/bash

exec 2>>/tmp/trace.log; PS4=':${BASH_SOURCE}:$LINENO+'; set -x

# Example output:
#  /media/me/whatever
#  /media/me/whatever2
#
lsblkOutput=$(lsblk -J -o MOUNTPOINT | \
              jq -r '.blockdevices[] | select(.mountpoint != null) | .mountpoint | select(startswith("/media"))');

nameToShow=""
deviceMountpoint=""

toConky=""

if [[ "$lsblkOutput" == "" ]]
then
  exit
fi

# To array
IFS=$'\n' devices=($lsblkOutput)
for device in "${devices[@]}"
do
    # Remove the path...
    nameToShow=${device##*/}
    # If the name has several words, only show the first one.
    nameToShow=${nameToShow%\ *}
    # In case the device has whitespaces in the name -> escape them
    device=$(printf "%q" "$device")

    toConky="$toConky\${template3 $nameToShow $device}\n"
done

printf '%b' "$toConky"

Script trace when the external drive is inserted:

::./conkscript/find-removable-devices.sh:10+jq -r '.blockdevices[] | select(.mountpoint != null) | .mountpoint | select(startswith("/media"))'
::./conkscript/find-removable-devices.sh:10+lsblk -J -o MOUNTPOINT
:./conkscript/find-removable-devices.sh:10+lsblkOutput=/media/acs/TOSHIBA
:./conkscript/find-removable-devices.sh:12+nameToShow=
:./conkscript/find-removable-devices.sh:13+deviceMountpoint=
:./conkscript/find-removable-devices.sh:15+toConky=
:./conkscript/find-removable-devices.sh:17+[[ /media/acs/TOSHIBA == '' ]]
:./conkscript/find-removable-devices.sh:23+IFS='
'
:./conkscript/find-removable-devices.sh:23+devices=($lsblkOutput)
:./conkscript/find-removable-devices.sh:24+for device in "${devices[@]}"
:./conkscript/find-removable-devices.sh:27+nameToShow=TOSHIBA
:./conkscript/find-removable-devices.sh:29+nameToShow=TOSHIBA
::./conkscript/find-removable-devices.sh:31+printf %q /media/acs/TOSHIBA
:./conkscript/find-removable-devices.sh:31+device=/media/acs/TOSHIBA
:./conkscript/find-removable-devices.sh:33+toConky='${template3 TOSHIBA /media/acs/TOSHIBA}\n'
:./conkscript/find-removable-devices.sh:36+printf %b '${template3 TOSHIBA /media/acs/TOSHIBA}\n'

Script trace when the external drive is removed:

::./conkscript/find-removable-devices.sh:10+jq -r '.blockdevices[] | select(.mountpoint != null) | .mountpoint | select(startswith("/media"))'
::./conkscript/find-removable-devices.sh:10+lsblk -J -o MOUNTPOINT
:./conkscript/find-removable-devices.sh:10+lsblkOutput=
:./conkscript/find-removable-devices.sh:12+nameToShow=
:./conkscript/find-removable-devices.sh:13+deviceMountpoint=
:./conkscript/find-removable-devices.sh:15+toConky=
:./conkscript/find-removable-devices.sh:17+[[ '' == '' ]]
:./conkscript/find-removable-devices.sh:19+exit
Adri C.S.
  • 2,909
  • 5
  • 36
  • 63
  • Variables are not automatically expanded inside strings that are returned from a command. You need to replace `$USER` in `LSBLK_OUTPUT` with the actual username. – Barmar Oct 06 '17 at 22:50
  • 1
    It looks like `lsblk` output is JSON. You should use a tool like `jq` to parse it, rather than doing it ad hoc with `awk` and `tr`. – Barmar Oct 06 '17 at 22:51
  • @Barmar Changed, but Conky keeps complaining. – Adri C.S. Oct 06 '17 at 22:54
  • Sure -- that was a note on good practices. If it were an answer, it'd be an *answer*, not a comment. – Charles Duffy Oct 06 '17 at 22:55
  • Does `/media/acs/TOSHIBA` exist? – Barmar Oct 06 '17 at 22:56
  • 1
    BTW, have you considered logging a trace of your script's execution? `exec 2>/path/to/trace.log; PS4=':${BASH_SOURCE}:$LINENO+'; set -x` up by the top might do some good in terms of helping to isolate a narrower question that can be put into [mcve] form (runnable by folks who don't know/have/want "Conky"). – Charles Duffy Oct 06 '17 at 22:57
  • Of course. It is my external drive and it is mounted. When unmounted, conky shows that message. – Adri C.S. Oct 06 '17 at 22:57
  • 1
    BTW, speaking of best practices -- `echo -en "$foo"` is better replaced with `printf '%b' "$foo"`. See the APPLICATION USE and RATIONALE sections in the [POSIX spec for `echo`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html). – Charles Duffy Oct 06 '17 at 22:59
  • 1
    (And all-caps variables are used by variables with meaning to the shell or OS, whereas names with at least one lowercase character are reserved for application use; see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html) – Charles Duffy Oct 06 '17 at 23:01
  • 1
    BTW, why `while read -r media; do read -ra media_t <<<"$media"` instead of just `while IFS=',' read -ra media_t; do`? Herestrings aren't free -- when you use `<<<"$media"` on the inner `read`, that creates a temporary file to write the contents to. – Charles Duffy Oct 06 '17 at 23:03
  • @CharlesDuffy Thanks for your suggestions. I will change the script accordingly! – Adri C.S. Oct 06 '17 at 23:16
  • Is there useful/relevant content in that `trace.log`? Can you, for instance, find the line that's generating the error? – Charles Duffy Oct 07 '17 at 22:13
  • @CharlesDuffy Edited the question with the script traces (when inserting the external drive and when removing it). The error seems to come from the Conky side. It's like it "caches" the drives that are mounted and when they are removed, it complains until they get mounted again. – Adri C.S. Oct 07 '17 at 22:18
  • It does indeed look like the script's doing just fine (and that issues are internal to Conky). – Charles Duffy Oct 07 '17 at 22:25
  • 1
    BTW, not the immediate problem, but safer to use `readarray -t devices <<<"$lsblkOutput"` (or to put content there directly without intermediate storage in a string at all, thus avoiding the need for a herestring; ie. `readarray -t devices < <(lsblk ... | jq ...)`). With the existing code, you have unwanted glob expansion: if `lsblkOutput='*'`, then you'll get a list of files in the current directory in `devices`. – Charles Duffy Oct 07 '17 at 22:26

1 Answers1

1

There is an open issue with Conky regarding this problem.

Instead of using the available fs_* Conky commands, we can use the script to check for the size and percentage of use and inject the information in Conky:

for device in "${devices[@]}"
do
    nameToShow=${device##*/}
    nameToShow=${nameToShow%\ *}

    { read _header; read size used pcnt; } < <(df -h --output=size,used,pcent $device)

    toConky+="\${template3 $nameToShow $used $size $pcent}\n"
done

printf '%b' "$toConky"

Then, we just have to modify template3 in the Conky config file:

template3 = [[${font FontAwesome}${color orange}Disk:$color$font${voffset -2} \1 ${alignr 30} \2/\3 (\4)]]

Since Conky is never querying the device by itself but only printing text, it will not complain when a volume is unmounted.

Adri C.S.
  • 2,909
  • 5
  • 36
  • 63
  • 1
    Silly to run `df` and `tail` and `awk` three times. Consider instead: `{ read _header; read -r size used pcnt; } < <(exec df -h --output=size,used,pcent)`, which will put your content directly into shell variables `size`, `used` and `pcnt` while not creating any unnecessary subshells or invoking external tools other than a single instance of `df`. – Charles Duffy Oct 09 '17 at 15:19
  • Nice! Thanks for the suggestion! – Adri C.S. Oct 09 '17 at 15:20
  • 1
    BTW, you can also use `stringVar+="value"` to append to a string more efficiently than `stringVar="${stringVar}value"`. – Charles Duffy Oct 09 '17 at 15:21
  • 1
    I'd also suggest quoting `"$device"` on the call to `df`, and maybe even putting in `--` as a prior argument to make it unambiguously positional (thus, `< <(exec df -h --output=size,used,pcent -- "$device")`. Shouldn't make a difference assuming a sane value for IFS and a sane device name, but better to write code that doesn't require folks to analyze the environment's sanity to determine if it's correct. :) – Charles Duffy Oct 09 '17 at 15:32
  • 1
    BTW, the `exec` in my suggestions above is a performance optimization -- it ensures that the subshell generated for the process substitution is consumed during the invocation of `df`, rather than having the shell (potentially -- sometimes that optimization happens automatically, sometimes it doesn't, depending on version and a bunch of runtime configuration details) running an additional `fork()` after. – Charles Duffy Oct 09 '17 at 15:35