I am trying to test for the presence of custom firewall-rules on servers. I am doing this by processing the iptables output with awk and returning an appropriate exit-code. This works as intended, and the correct result is printed. However, when this is piped into tee, the conditional no longer works, and the result is printed every time.

# The broken code
ssh server1 'sudo iptables -L | awk "(!/ACCEPT/ && !/^target/ && !/^$/) {rules=1 ;} (rules==1) {exit 1}" && printf "%s\n" "string1" "string2" | sudo tee -a /path/to/file'
# The same code without `| sudo tee -a /path/to/file` works as intended
ssh server1 'sudo iptables -L | awk "(!/ACCEPT/ && !/^target/ && !/^$/) {rules=1 ;} (rules==1) {exit 1}" && printf "%s\n" "string1" "string2"'

What am I missing? I'm using GNU coretils 8.30 and GNU bash, version 4.4.20(1) on RHEL8. I've just noticed the awk statement could be shortened, but that's not the point :D

  • Are you sure it's not awk's output that is piped to tee in your actual code, rather than printf's? Jun 16, 2023 at 10:35
  • exactly, the first one has iptables | awk && printf | tee, so the tee looks to be outside the conditional, only seeing the output of printf. That shouldn't affect if ` printf` runs
    – ilkkachu
    Jun 16, 2023 at 10:36
  • Also ssh server1 server2 would run the server2 command on the server1 host. Please make sure the code you give in your question is representative of the actual command you ran. Jun 16, 2023 at 10:36
  • Note that none of ssh, awk, sudo are from GNU coreutils. Only tee there is. Jun 16, 2023 at 10:39
  • 3
    Try it as iptables | (awk && printf) | tee (i.e. run awk and printf as a grouped command in a subshell) or iptables | { awk && printf; } | tee (the semi-colon and the space after { are required) as a grouped command in the same shell. See man bash and search for the Compound Commands section. or read gnu.org/software/bash/manual/html_node/Command-Grouping.html
    – cas
    Jun 16, 2023 at 15:08

1 Answer 1


Your complaint centers around what status that iptables ... | awk ... && printf ... | tee ... pipeline returns. The bash documentation explains what its value will be.

The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully.

Consider executing

set -o pipefail

before piping results.

Alternatively, use the ... | (cmd ... || true) | ... idiom so each of the stages will return 0, if you find that simpler. Emit a distinctive message upon detecting an exceptional case, so a later pipeline stage can grep for it.

The design decision to have bash conditionally coordinate awk's simple processing and printf's simple processing maybe made sense at the time, but now you find it a drag on productivity. Consider combining that logic into a single, simple awk script.

You might find it more convenient to run shorter pipelines.

ssh server1 ... | awk ...  > server1.txt
# awk exit status is now available in $? so you can conditionally run printf

cat server1.txt  >> /path/to/file

In the original question /path/to/file is a server pathname, and we're running a one-liner remote pipeline of some complexity. You might find it more convenient to bring the raw iptables -L output back across the ssh connection, and run a local pipeline. Having produced a local results file, you can always store it with:

cat results.txt | ssh server1 'cat >> /path/to/file'

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .