Skip to content

Instantly share code, notes, and snippets.

@michael-simons
Last active July 1, 2020 13:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save michael-simons/433ae7da647d14135c73d44ee965f6a0 to your computer and use it in GitHub Desktop.
Save michael-simons/433ae7da647d14135c73d44ee965f6a0 to your computer and use it in GitHub Desktop.
A bash script waiting for port 7687 to become available without netcat or similar.
#!/bin/bash
until (exec 3<>/dev/tcp/127.0.0.1/7687) &>/dev/null; do
echo "Try again"
sleep 1s
done
@mkhl
Copy link

mkhl commented Jul 1, 2020

So, code review.

!#/bin/bash

The funny thing about this typo is that is doesn't usually matter for common usage, but…
The correct way to indicate the interpreter for a script file is by starting the first line with #! (not !#).
If I remember correctly, your current shell will default to using itself of there is no such line, which means if you're running this from a bash-compatible shell you won't notice that you haven't entered a shebang line but a single ! command followed by a line comment. Which will result in a non-zero exit status which is subsequently ignored.
I do believe it's important (or at least reasonable) to indicate that you require bash and not just any POSIX-compatible shell, say, so I strongly recommend using a proper shebang.

(Fun fact: The origin of the shebang is with Magic Numbers, but that's probably a story for another time.)

until [ … ];

A very common misconception about shell script is that the brackets are part of the conditional command syntax, but that is not the case. One can use any command as one's condition, and the brackets ([ and [[) happen to be commands that 1) are often useful for conditionals and 2) happen to require the corresponding closing brackets (] or ]]) as part of its syntax. And yes, [ or ]] are perfectly valid and reasonable command names.
(N.B. When using bash it's highly recommended to prefer [[ over [.)

`… && echo -n 'foo' || echo -n 'bar'` = 'foo'

When using a command as one's condition, its exit status determines success: A zero status is considered success and a non-zero status is considered failure.
This construct runs some command and generates a string depending on its exit status, then compares that to the string corresponding to success, resulting in an exit status for the comparison, and because it expects the string corresponding to success this will be the same status as that of the inner command.
(N.B. It actually won't, but either both statuses will be zero or both will be non-zero, which is what we care about for this script.)
This means that we can use the inner command and omit the rest of the construct.

>/dev/null 2>&1

In bash this can be abbreviated to &>/dev/null, and since this script requires bash features anyway that's a safe transformation to make here.

`{ …; }`

The command substitution (backticks) was required because of the transformation into a string that we then compared. Omitting that makes the command substitution unnecessary.
The group command ({ …; }) runs the grouped commands in the current shell environment, but the command substitution runs in a subshell so those commands were run in a subshell. And running them in a subshell is reasonable because exec modifies the current shell environment.
This means that when we remove the command substitution we need to turn the group command into a subshell command ((…)).

echo "Try again"

I personally prefer commands that aren't chatty by default, instead diagnosing them with set -x if necessary, which is why I recommended echo -n .. But that is a personal preference so 🤷

@michael-simons
Copy link
Author

Oh wow, @mkhl, you rock :)

The only thing I can answer right now is the typo: Damn! The rest, I have to read up.
I was inclined to replace the script with you fork, but yesterday, GH had it's own issues (lots of 500er).

@michael-simons
Copy link
Author

Oh and I actually noticed that one time pushing the button actually went through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment