Few confusions about this script

host=www.google.com
port=443
while true; do
    current_time=$(date +%H:%M:%S)
    r=$(bash -c 'exec 3<> /dev/tcp/'$host'/'$port';echo $?' 2>/dev/null)
    if [ "$r" = "0" ]; then
        echo "[$current_time] $host $port is open"
    else
        echo "[$current_time] $host $port is closed"
        exit 1 # To force fail result in ShellScript
    fi
    sleep 60
done

I saw this script in order to continuously poll connections to port 443 on www.example.com and log the output. But I am not understanding how the value of r is calculated?

Let's decode

3<> /dev/tcp/'$host'/'$port'

echo $?

What I am thinking is that echo $? will always return exit status success=0. Regardless of success or failure of the previous command. That means the return value of this command will always be 0 and host port will always be open(I've tested and I know my concept is wrong here).

Any guidance to understand this please.

This 'exec 3<> /dev/tcp/'$host'/'$port';echo $?' is the complete command executed in a subshell (with bash -c, notice the variables are expanded outside of ' '), thus if exec 3<> /dev/tcp/www.google.com/443 fails for any reason (returns with a non-zero exit status), then echo $? will not display 0 - and this other value will actually be assigned to r variable.

1 Like

I think you can save the other bash

r=$( { exec 3<> /dev/tcp/$host/$port; } 2>/dev/null; echo $? )
if [ "$r" = "0" ]; then

and further consolidate it to

if ( exec 3<> /dev/tcp/$host/$port ) 2>/dev/null
then

i.e. directly take the exit status of the exec that for safety still runs in a ( subshell ).

What does this do?
Specially that 3<> what kind of redirection operator is that?

So, I think I am near to understanding it.

exec 5<> file
means

  • open file for reading and writing and assign it fd=5.

So here it'd mean.
Open file /dev/tcp/www.google.com/443 for reading and writing.

But what then?

There is nothing called /dev/tcp in my linux box but still the command works. Why?

1 Like

man bash

REDIRECTION
[...]
Bash handles several filenames specially when they are used in redirections,
as described in the following table.  If the operating system on which bash
is running provides these special files, bash will use them; 
**otherwise it will emulate them internally** with the behavior described below.
[...]
              /dev/tcp/host/port
                     If host is a valid hostname or Internet address,
and port is an integer port number or service name, bash attempts
to open the corresponding TCP socket.
2 Likes

It's a bash trick to provide direct network access to the shell builtins.
For example you could send E-mail without a sendmail or mailx helper. In reality this is a very complicated way - I guess nobody has ever done it.
But it's good for a simple connection test.

1 Like

What I don't get is the command exec 3<> /dev/tcp/host/port should return an exit status. And even if I remove echo $?, I should get exit status because it is a command. If it's success, I should get exit status as 0 in r.

    r=$(bash -c 'exec 3<> /dev/tcp/'$host'/'$port')

This should give me exit status of r as 0 or non-zero values. Should not it? Why doesn't it give exit status?

Your code echos the exit status from the exec as a value (to stdout).
The value (the stdout) is stored in the variable. The following if compares the value.

See my previous post, the last consolidated sample:
the exec sets an exit status, the sub shell exits with it, the if branches on it.

Command substitution syntax $( ) is used to pass on to the caller (or assign as a value to a variable) the standard output of commands executed between ( ), whatever these commands would normally display on screen - if there's no echo command inside ( ), then there's no standard output to pass on, only the exit code (implicit), which is surely stored in $? special parameter (i.e. in memory), but it's never explicitly displayed on screen by its own (that is - until you explicitly echo / printf it, or put it inside stdout using any other know method).

1 Like

I am getting it slowly. I need to relearn bash again. I thought I knew bash by writing some stupid domain restart scripts.

I learned bash 5 times at least :grin:

My last learning, for the advanced bashist:
a normal assignment var=value does not overwrite a prior exit status, e.g.

var=$( grep xyz /notthere )
echo $?

shows the exit status 2 of the grep command.
But if prefixed by declare/typeset/readonly/local then the behavior changes: the exit status says if the special assignment was successful; the prior exit status is lost.
Shellcheck warns about "masking return value" if you do

#!/bin/bash
if declare var=$( grep xyz /notthere )
then
  echo "grep was successful and returned $var"
fi

Indeed the declare is kind of a command, setting a new exit status.
Correct is

#!/bin/bash
if
  declare var
  var=$( grep xyz /notthere )
then
  echo "grep was successful and returned $var"
fi

But in the case of readonly this does not work. (Why?)

2 Likes

But what exactly doesn't work?

I initially assumed "Why?" as a rhetorical question, but let me elaborate :slight_smile:
This is mostly because readonly (as the command's name suggests) declares the variable to be read-only i.e. immutable.

It doesn't matter, whether you declare an "empty variable" as a readonly, e.g.

readonly var

or if you declare it readonly and assign it a value at the same time, e.g.

readonly var="$(grep xyz /notthere)"

It's just that if you once declared a variable using readonly command, then any susbsequent assignment statement (attempting to assign a new value) to this variable is erroneus, e.g.

readonly var
var="$(grep xyz /notthere)"

and will always return a non-zero exit status, as the variable cannot be successfully assigned another value, because it's read-only = immutable already.

Or did I misunderstand the "why?" (If it was addressed specifically to someone else, then I'm sorry.)

It was a quiz question for the bashists out there. You answered it well :slight_smile:

For my exit status problem, I found the following workaround:

#!/bin/bash
if
  var=$( grep xyz /notthere )
  exitsave=$?
  readonly var
  [ $exitsave -eq 0 ]
then
  echo "grep was successful and returned $var"
fi

Of course you can move the first 3 statements before the if