0

I'm making a script to print ZFS filesystem info - currently in the testing phase, and I'm getting a strange error.

The relevant bit of my initial script is this:

zfs_human="$(zfs list | head -n 2 | tail -n 1)"
dfs_human="$(df -h | grep 'zfs' | head -n 1)"
zfs_usedh="$(echo $zfs_human | cut -d ' ' -f2)"
zfs_totah="$(echo $dfs_human | cut -d ' ' -f2)"
echo "$zfs_human"
echo "$dfs_human"
echo "$zfs_usedh"
echo "$zfs_totah"

Giving the following output:

zfs                      2.31M  5.27T     34.4K  /mnt/zfs
zfs                      5.3T  128K  5.3T   1% /mnt/zfs
2.31M
5.3T

However, when I run shellcheck, it says I should double-quote the variable names inside the command substitution, this is the output from shellcheck:

In zfsspace.sh line 5:zfs_usedh="$(echo $zfs_human | cut -d ' ' -f2)"                  ^--------^
SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
zfs_usedh="$(echo "$zfs_human" | cut -d ' ' -f2)"

In zfsspace.sh line 6:zfs_totah="$(echo $dfs_human | cut -d ' ' -f2)"                  ^--------^
SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
zfs_totah="$(echo "$dfs_human" | cut -d ' ' -f2)"

Then I of course change my code to shellcheck's recommendation:

zfs_human="$(zfs list | head -n 2 | tail -n 1)"
dfs_human="$(df -h | grep 'zfs' | head -n 1)"
zfs_usedh="$(echo "$zfs_human" | cut -d ' ' -f2)"
zfs_totah="$(echo "$dfs_human" | cut -d ' ' -f2)"
echo "$zfs_human"
echo "$dfs_human"
echo "$zfs_usedh"
echo "$zfs_totah"

But now the output is this:

zfs                      2.31M  5.27T     34.4K  /mnt/zfs
zfs                      5.3T  128K  5.3T   1% /mnt/zfs
 
 

Line 3 and 4 is blank, which means the 3rd and 4th command substitution does not work when following shellcheck's recommendation, but works when not quoting the variable that I echo.

I'm using Bash 5.0.17 on Ubuntu 20.04.1

Can anyone explain this please??? Thanks.

6
  • 3
    I suspect it's because the unquoted $zfs_human is subject to split+glob, as a result of which sequences of whitespace are replaced with the first character of the current IFS (by default, a single space). The quoted "$zfs_human" will retain whatever whitespace is output by the zfs list command. You could likely avoid the issue by using awk (which treats contiguous whitespace as the delimiter by default) instead of cut -d ' ' – steeldriver Sep 27 '20 at 20:27
  • Ok thanks - or I could make a directive to ignore the shellcheck warning in this particular instance of course. – Artur Meinild Sep 27 '20 at 20:37
  • looks that you are trying to print line two head ... tail ... probably easier to use ... | sed -n 2p – bac0n Sep 28 '20 at 5:37
  • you could do read -r filesystem size used avail _ < <(df -hP | grep -m1 zfs) ... read will squeeze spaces for you. – bac0n Sep 28 '20 at 5:49
  • ...but my first choice would be to use lsblk. – bac0n Sep 28 '20 at 6:41
1

Adding -H to zfs will remove the headers and separate fields by a single tab instead of arbitrary whitespace, which simplifies parsing considerably. Because df don't have an option for removing the header, findmnt will be a better choice.

#!/bin/bash

# zfs
read -r zfs_usedh < <(zfs list -H -o used)

# df
read -r zfs_totah < <(findmnt -frnt zfs -o size)

echo "$zfs_usedh"
echo "$zfs_totah"

There is no total size, as with df so, you have to fall back on traditional math.

#!/bin/bash

read -d \\n -r avail used total < <(zfs get -Hpo value available,used | \
     awk 'NR < 3 { n+=$1; print $1 } END { print n }' | numfmt --invalid=ignore --to=iec \
)

printf %s\\n $used $total
2
  • Fairly certain zfs can produce total size too, then you could use a single read, just add a new variable for every output field. – bac0n Sep 28 '20 at 19:48
  • Thanks. If you find the parameter for ZFS total size, please let me know. I haven't been able to find it. – Artur Meinild Sep 29 '20 at 6:57
0

As per @steeldrivers suggestion, I replaced cut with awk, and this works as intended.

zfs_human="$(zfs list | head -n 2 | tail -n 1)"
dfs_human="$(df -h | grep 'zfs' | head -n 1)"
zfs_usedh="$(echo "$zfs_human" | awk -F " " '{print $2}')"
zfs_totah="$(echo "$dfs_human" | awk -F " " '{print $2}')"
echo "$zfs_human"
echo "$dfs_human"
echo "$zfs_usedh"
echo "$zfs_totah"

For consistency, I believe this is the best solution to preserve the syntax recommended by shellcheck, thus consistently using awk over cut for string splitting, unless there is a specific reason to use cut.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.