-1

I've this simple bash variable, in which i'm just checking if a port is open on a host, then if it's open the variable is the first host, else it's the second one.

Case 1:

set -x;
var=$(if $(netcat -zvw2 1.1.1.1 53  2>&1 | grep -o open);
then var="1.1.1.1"; else var="8.8.8.8";
fi;
echo $var);
set +x;
echo $var
+++ netcat -zvw2 1.1.1.1 53
+++ grep -o open
++ open
++ var=1.1.1.1
++ echo 1.1.1.1
+ var=1.1.1.1
+ set +x
1.1.1.1

Case 2:

set -x;
var=$(if $(netcat -zvw2 1.1.1.1 53  2>&1 | grep -o open);
then var="1.1.1.1"; else var="2.2.2.2";
fi);
set +x;
echo $var
+++ netcat -zvw2 1.1.1.1 53
+++ grep -o open
++ open
++ var=1.1.1.1
+ var=
+ set +x

What I do not understand is why in case2 the variable gets reset.

I mean, in case1 I'm echoing the result just obtained and so I'm "forcing it" as the variable output, but in case2 I suppose the result should still be kept after the 'if loop', intead as the trace shows, "var" becomes empty.

Probably it's something really basic about how bash works that I'm missing.

I believe that this is not the exact same thing as "shopt -s lastpipe" that I found in my previous research on StackOverflow.


UPDATE: "open" is clearly a typo (as KamilCuk noticed), I just didn't notice because "open" exists on my system as a part of the "mail-utils" package. I didn't even know about this command until few moments ago. It just gives a empty result if launched without any parameter so it wasn't giving any error.

This edit below should fix the above twisted logic and command substition abuse:

if [ -n "$(netcat -zvw2 1.1.1.1 53 2>&1 | grep -o open)" ]; then var="1.1.1.1"; else var="8.8.8.8"; fi
Andrea
  • 35
  • 6
  • 2
    Does this answer your question? [Set a parent shell's variable from a subshell](https://stackoverflow.com/questions/15541321/set-a-parent-shells-variable-from-a-subshell) – Aserre Jan 27 '22 at 10:13
  • 1
    That is odd, don't you get `open: command not found` message? Do you have `open` command? Overall `if $(..)` is _executing the result_ of `netvar | grep` as another command. You want `if netcart ... | grep -q ..` without `$( )` – KamilCuk Jan 27 '22 at 10:13
  • 1
    The immediate problem is that you never `echo "$var"` in the subshell in the second case, so the output from `$(...)` is the empty string. No mystery at all here. Voting to close as trivial typo. – tripleee Jan 27 '22 at 10:26
  • More generally, that's horrible pretzel logic; maybe see [useless use of `echo`](https://www.iki.fi/era/unix/award.html#echo) – tripleee Jan 27 '22 at 10:27
  • 1
    **Always** quote sub-shell executions, especially if they are nested this way: `var="$(... "$(... )")"`. And use **functions**! – ceving Jan 27 '22 at 10:42

1 Answers1

1

The command substitution $(...) is replaced by the output of the command (hence the name).

var=$(
    ....
    echo $var
)
# executes `$(...)` and replaces it by the output
+ var=1.1.1.1
# assigns 1.1.1.1 to var

The second $(...) outputs nothing, so it is replaced by nothing. So var= assigns empty string to var, so var is empty (but set).

The other thing is, that $(...) is executed in a subshell. Changes in a subshell do not affect the parent shell.

In the same way, if $(netcat ... | grep ...); then is executing netcat ... | grep and then replaces it by the result, so it becomes if open; then. Then the command open is executed, which exists on your system (on mine it doesn't) and seems to return with success, so if succeeds. You want: if netcat ... | grep -q ...; then - you want to check if grep succeeds, not if the string that returns grep that is executed as a command succeeds. I.e. if $(echo true | grep false); then will fail, because false command will fail.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks for the explation but wait... I believe you misread something. I didn't execute the 'open' command, I did just grep the "open" string. You can clearly see it by the command "| grep -o open)" Where did you read the 'open' command? – Andrea Jan 27 '22 at 13:35
  • 1
    `++ open` "clearly" shows open command is executed. You did not misread. `if command` executes the command. So `if $(command)` executes `command`, then replaces `$(..)` by result of `command` and executes the result as another command (after word splitting and filename expansion). I.e. `if $(echo echo 123)` executes `if echo 123` which prints `123`. – KamilCuk Jan 27 '22 at 13:52
  • 1
    If you want to check if output of a command is not empty, you execute `[` or `test` or `[[` command, like `if [ -n "$(stuff)" ]` or `if test -n "$(stuff)"` or `if [[ -n "$(stuff)" ]]`, but in this case `grep` exits with nonzero exit status in case of no match, so you can use it's exit status, and the exit status of the pipe is the exit status of right-most command. So just `if stuff | grep` – KamilCuk Jan 27 '22 at 13:56
  • yes I understand your comment. That is clearly a typo. $(...) output the result and executes. I didn't notice because on my system by accident "open" exists, and it's just a "mail-utils" command (that does nothing when not given any parameter). I didn't even know the command existed. In that case, as you said, clearly command substition is totally futile and can be superseded just by "if netcat ... | grep ..." – Andrea Jan 27 '22 at 14:20
  • this should fix the above nonsense in a more concise way: if [ -n "$(netcat -zvw2 1.1.1.1 53 2>&1 | grep -o open)" ]; then dcvar="1.1.1.1"; else dcvar="2.2.2.2"; fi && echo $dcvar – Andrea Jan 27 '22 at 14:41