In the following script, I am using bash to check whether users own their home directories as part of the CIS CentOS 8 Benchmark (6.2.8).
#!/bin/bash
grep -E -v '^(halt|sync|shutdown)' /etc/passwd | awk -F: '($7 != "'"$(which nologin)"'" && $7 != "/bin/false") { print $1 " " $6 }' | while read user dir; do
if [ ! -d "$dir" ]; then
echo "The home directory ($dir) of user $user does not exist."
else
owner=$(stat -L -c "%U" "$dir")
if [ "$owner" != "$user" ]; then
echo "The home directory ($dir) of user $user is owned by $owner."
fi
fi
done
I am trying to print something if there are no errors using a global variable. The following is my attempt at it:
correct=true
grep -E -v '^(halt|sync|shutdown)' /etc/passwd | awk -F: '($7 != "'"$(which nologin)"'" && $7 != "/bin/false") { print $1 " " $6 }' | while read user dir; do
if [ ! -d "$dir" ]; then
echo "The home directory ($dir) of user $user does not exist."
correct=false
else
owner=$(stat -L -c "%U" "$dir")
if [ "$owner" != "$user" ]; then
echo "The home directory ($dir) of user $user is owned by $owner."
correct=false
fi
fi
done
if [ "$correct" = true ]; then
echo "Non-compliance?: No"
echo "Details: All users own their home directories."
echo
fi
However, the global variable, correct
, will not change regardless of what happens in the while
loop because it is in multiple sub-shells.
I read up about this and noticed people using "here strings" so that the while
loop will not be in a sub-shell. However, for my case I have multiple pipes (possibly might even add more for other scripts), so I don’t really know how to make it do what I want here.
How can I get results information out of a loop
so I can display summary information after the loop completes
when the loop is executed in a sub-shell?
2
Answers
This How is return handled in a function while loop?, and some rewriting led to this script, which can return
false
:user Horatio Altman might have a username of
haltman
.Your code would ignore him since his name begins with
halt
.You should use a regular expression
of
'^(halt|sync|shutdown):'
.Note the colon at the end — this will match only lines that have
halt
,sync
orshutdown
as the first field in the:
-delimited file.awk
is a very powerful program.You almost never need to combine it with anything like
grep
orsed
;awk
can do (pretty much?) anything they can do.In your script, we can eliminate the
grep
and put the
halt
/sync
/shutdown
logic intoawk
:Since
$1
means everything up to the first colon (since we have-F:
)and we are doing simple string equality checking
(rather than regexp matching),
we don’t need the
^
at the beginning or the:
at the end.Also note that we can add an Enter after a
|
without needing a backslash.
and easy to get wrong, especially when you edit the script a year from now.
A cleaner way to inject information into an
awk
script is to use a variable:Here I created an awk variable called
nl
with the value of
$(which nologin)
,and then used that variable in the comparison against
$7
.(Also I broke that very long line with a backslash, for readability.)
As you understand, the problem with your script is that
it sets the
correct
variable inside a subshelland then tries to access it outside the subshell.
One solution is to move the statement(s) that access the variable
into the subshell.
You might think that this is infeasible,
since the subshell is the
while
loop,and you don’t want to print the success message inside the loop.
The trick is to force a larger subshell:
I added parentheses to force a subshell that encompasses
the
while
loop and theif-then
statement.I put the parentheses on separate lines for readability;
you can move them onto the preceding line or the following line
if you want to minimize your line count.
(The next code block demonstrates this.)
$correct
at some point much later in the script?
You don’t want to move large, unrelated chunks of code into the subshell
just so you can use this trick.
Well, my next trick is to pass information out of the subshell
by using its exit status:
In theory, exit codes can range from 0 to 255,
although it’s best to limit yourself to the 0-125 range.
See What is the min and max values of exit codes in Linux?