Shellcheck recommendation to double-quote variable does not work when piping to cut

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

2 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.

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

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

You Might Also Like