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.3THowever, 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.
62 Answers
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 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.