从shell脚本测试远程TCP端口是否开放

374

我正在寻找一种快速简单的方法来在Shell脚本内正确测试给定TCP端口是否在远程服务器上打开。

我已经使用telnet命令完成了这个任务,并且当端口打开时它可以正常工作,但是当它没有超时并且只是挂起时就不行了...

下面是一个示例:

l_TELNET=`echo "quit" | telnet $SERVER $PORT | grep "Escape character is"`
if [ "$?" -ne 0 ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

我需要一种更好的方法,或者一种能够强制telnet在8秒内连接并返回可以在Shell中捕获的东西(返回码或stdout字符串)的方法。

我知道Perl方法,它使用IO::Socket::INET模块并编写了一个成功测试端口的脚本,但如果可能的话,我宁愿避免使用Perl。

注意:这是我的服务器正在运行的内容(我需要从该服务器运行此内容)

SunOS 5.10 Generic_139556-08 i86pc i386 i86pc


1
那么 Netcat 或者 Nmap 呢? - Jeremiah Willcock
答案在Expect中。我们编写了一个简单的脚本,发送了一个telnet到我们需要的端口,并设置了8秒的超时时间。还有很多例子可供选择。我们的脚本是基于这篇文章的:http://www.unix.com/shell-programming-scripting/146568-expect-telnet-testing-tacacs-cisco.html - Yanick Girouard
1
check_tcp可以从https://github.com/monitoring-plugins/monitoring-plugins进行下载,它可以执行此操作,包括输入字符串并检查预期的答案。 - user3323848
18个回答

536

正如B. Rhodes所指出的那样,ncnetcat)可以胜任此任务。更简洁的使用方法:

nc -z <host> <port>

这样,nc 只会检查端口是否开放,成功时退出并返回 0,失败时返回 1。

进行快速互动检查(超时时间为 5 秒):

nc -z -v -w5 <host> <port>

49
CentOS 7 默认使用 nmap 和 netcat,但没有 -z 选项。 - jolestar
8
这在RHEL/Centos中不起作用。对于这些发行版,您需要使用以下命令: nc -vn <host> <port> - Justin Dennahower
3
我已经完全改写了我的答案,并提供了一个示例,该示例适用于RHEL 6和RHEL 7。 - Asclepius
6
在Mac上,你可能需要添加"-G#"以设置一个连接超时时间,该超时时间与/或是"-w#"超时时间分开,后者基本上用作读取超时时间。 - Doktor J
1
@jolestar 你可以手动升级Centos 7上的Ncat以获得-z选项。你可能想考虑这个链接:https://unix.stackexchange.com/questions/393762/how-to-manually-update-nmaps-netcat-on-centos-7 - Damien
显示剩余9条评论

199
使用-z-w TIMEOUT选项与nc一起做起来很容易,但并非所有系统都安装了nc。如果您有足够新的bash版本,这将起作用:
# Connection successful:
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/80'
$ echo $?
0

# Connection failure prior to the timeout
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/sfsfdfdff.com/80'
bash: sfsfdfdff.com: Name or service not known
bash: /dev/tcp/sfsfdfdff.com/80: Invalid argument
$ echo $?
1

# Connection not established by the timeout
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/81'
$ echo $?
124

这里发生的情况是,timeout将运行子命令,并在指定的超时时间内(上面的示例中为1秒)如果子命令没有退出,则终止它。在这种情况下,bash是子命令,并使用其特殊的/dev/tcp处理来尝试打开到指定服务器和端口的连接。如果bash能够在超时时间内打开连接,cat将立即关闭它(因为它从/dev/null读取),并以状态码0退出,该状态码将传递给bash,然后传递给timeout。如果bash在指定的超时时间之前出现连接失败,则bash将以退出代码1退出,timeout也将返回相同的退出代码。如果bash无法建立连接并且指定的超时时间过期,则timeout将终止bash并以状态码124退出。
对于Git Bash,请使用不同的语法。
$ timeout 1 bash -c '< /dev/tcp/google.com/80'
$ echo $?
0 

否则,Git Bash会返回一个意料之外的错误:
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/80'
$ echo $?
124

2
除了Linux系统,其他系统是否支持/dev/tcp?特别是Mac系统呢? - Peter
12
/dev/tcp 是 bash 的一个特性,是的。然而看起来 Mac 没有超时功能... - onlynone
1
看起来 timeout 在 FreeBSD 中也不存在,在我的旧 Ubuntu 上它是一个可安装的包,但默认情况下没有安装。如果有一种不需要第三方工具(如 timeout 或 netcat)的方法可以在 bash 中完成就太好了。 - Graham
3
想提一下,timeout似乎是GNU coreutils的一部分,并且可以通过homebrew在Mac上安装:brew install coreutils。然后它将作为gtimeout可用。 - onlynone
1
@Wildcard 一个bash的变更日志建议使用bash-2.04-devel,尽管主机名的支持可能已经在bash-2.05-alpha1中添加。 - Asclepius
显示剩余4条评论

135

目录:

  • 使用bash和timeout
    • 命令
    • 示例
  • 使用nc
    • 命令
    • RHEL 6 (nc-1.84)
      • 安装
      • 示例
    • RHEL 7 (nmap-ncat-6.40)
      • 安装
      • 示例
  • 备注

使用bash和timeout

请注意,timeout应该在RHEL 6+中存在,或者在GNU coreutils 8.22中找到。在MacOS上,使用brew install coreutils进行安装,并将其用作gtimeout

命令:

$ timeout $TIMEOUT_SECONDS bash -c "</dev/tcp/${HOST}/${PORT}"; echo $?

如果参数化主机和端口,请确保将它们指定为${HOST}${PORT},就像上面那样。不要仅仅指定它们为$HOST$PORT,即没有大括号;在这种情况下这是不起作用的。

示例:

成功:

$ timeout 2 bash -c "</dev/tcp/canyouseeme.org/80"; echo $?
0

失败:

$ timeout 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?
124

如果你必须要保留bash的退出状态,

$ timeout --preserve-status 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?
143

使用 nc:

请注意,RHEL 7 上安装的 nc 版本不兼容之前版本。

命令:

请注意,下面的命令在 RHEL 6 和 7 中完全相同,只是安装和输出不同。

$ nc -w $TIMEOUT_SECONDS -v $HOST $PORT </dev/null; echo $?

RHEL 6 (nc-1.84):

安装:

$ sudo yum install nc

示例:

$ nc -w 2 -v canyouseeme.org 80 </dev/null; echo $?
Connection to canyouseeme.org 80 port [tcp/http] succeeded!
0

$ nc -w 2 -v canyouseeme.org 81 </dev/null; echo $?
nc: connect to canyouseeme.org port 81 (tcp) timed out: Operation now in progress
1

如果主机名映射到多个IP,则上述失败命令将循环遍历其中的许多或所有IP。例如:
$ nc -w 2 -v microsoft.com 81 </dev/null; echo $?
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
1

RHEL 7 (nmap-ncat-6.40):

安装:

$ sudo yum install nmap-ncat

例子:

$ nc -w 2 -v canyouseeme.org 80 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connected to 52.202.215.126:80.
Ncat: 0 bytes sent, 0 bytes received in 0.22 seconds.
0

$ nc -w 2 -v canyouseeme.org 81 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connection timed out.
1

如果主机名映射到多个IP地址,上述失败命令将循环遍历其中的许多或全部IP地址。例如:

$ nc -w 2 -v microsoft.com 81 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connection to 104.43.195.251 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 23.100.122.175 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 23.96.52.53 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 191.239.213.197 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection timed out.
1

备注:

-v--verbose)参数和echo $?命令仅用于说明作用。


3
timeout 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $? 是一个很棒的命令! - ipeacocks
1
2>&1 添加到不回显状态中 result_test=$(nc -w 2 $ip_addreess 80 </dev/null 2>&1 ; echo $?) - snex

64

使用 netcat 命令可以检查端口是否开启,方法如下:

nc my.example.com 80 < /dev/null
nc的返回值会在成功打开TCP端口时返回成功,若无法建立TCP连接则返回失败(通常是返回代码1)。

一些版本的nc会出现异常,因为它们即使在接收到来自/dev/null的文件结束标志后仍不会关闭其套接字的发送端。 在我的Ubuntu笔记本电脑(18.04)上,我安装的netcat的版本提供了一个解决方法:使用-N选项可以获得即时结果:

nc -N my.example.com 80 < /dev/null

2
适用于不支持“-w”标志的“nc”版本,效果非常好。 - David Dossot
5
谢谢。这也适用于不支持“-z”选项的nc版本。例如,在RHEL/CentOS 7上可以使用。 - Andrew
1
虽然这种方法很好,但是-w是必需的,否则在脚本中使用该命令可能会挂起超过十秒钟。我已经写了一个答案,其中包括-w - Asclepius
2
+1 这很棒,因为nc在Alpine Linux和Ubuntu中都是标准的。可能还有其他的操作系统也是如此。当你远程登录并且无法安装额外软件时,就不需要安装额外的软件了。感谢您!我可能会添加 nc host port -w 2 && echo it works - std''OrgnlDave
3
我更喜欢使用nc -vz localhost 3306,因为它提供了更详细的输出。我只在Mac上尝试过。 - EFreak
显示剩余6条评论

39
在Bash中使用伪设备文件进行TCP/UDP连接非常简单。以下是脚本:
#!/usr/bin/env bash
SERVER=example.com
PORT=80
</dev/tcp/$SERVER/$PORT
if [ "$?" -ne 0 ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

测试:

$ ./test.sh 
Connection to example.com on port 80 succeeded

这是一行代码(Bash语法):

</dev/tcp/localhost/11211 && echo Port open. || echo Port closed.

请注意,一些服务器可能会受到SYN洪水攻击的防火墙保护,因此您可能会遇到TCP连接超时问题(约75秒)。为了解决超时问题,请尝试以下方法:
timeout 1 bash -c "</dev/tcp/stackoverflow.com/81" && echo Port open. || echo Port closed.

请参见:如何降低TCP connect()系统调用的超时时间?


1
@A-B-B 这与服务器的防火墙配置有关,以防止任何 SYN 洪水攻击而不给予任何响应。运行 telnet canyouseeme.org 81 也会挂起。这是由你的超时限制控制的,可能在 bash 中编码硬编码。请参见:降低 TCP connect()系统调用超时 - kenorb
为了参数化,现在似乎需要用大括号指定$SERVER$PORT,至少要写成${SERVER}${PORT} - Asclepius

15

我需要一种更灵活的解决方案来处理多个Git仓库,因此我基于12编写了以下sh代码。 您可以使用您的服务器地址代替gitlab.com,并使用您的端口替换22。

SERVER=gitlab.com
PORT=22
nc -z -v -w5 $SERVER $PORT
result1=$?

#Do whatever you want

if [  "$result1" != 0 ]; then
  echo  'port 22 is closed'
else
  echo 'port 22 is open'
fi

1
谢谢!这让我的生活变得更加轻松,可以在/etc/rc.local中使用。 - Shahin Khaled
1
对于一行代码,请使用 if [[ $( nc -z -v -w5 $SERVER 22 >/dev/null 2>&1; echo $?) -eq 0 ]]; then - ImranRazaKhan

14

使用Bash检查端口

示例

$ ./test_port_bash.sh 192.168.7.7 22

端口22是开放的

代码

HOST=$1
PORT=$2
exec 3> /dev/tcp/${HOST}/${PORT}
if [ $? -eq 0 ];then echo "the port $2 is open";else echo "the port $2 is closed";fi

12
如果您正在使用kshbash,它们都支持使用/dev/tcp/IP/PORT结构将IO重定向到/从套接字。在此Korn shell示例中,我将无操作符(:)的标准输入从套接字重定向:
W$ python -m SimpleHTTPServer &
[1]     16833
Serving HTTP on 0.0.0.0 port 8000 ...
W$ : </dev/tcp/127.0.0.1/8000

如果套接字未打开,shell 会打印一个错误信息:

W$ : </dev/tcp/127.0.0.1/8001
ksh: /dev/tcp/127.0.0.1/8001: cannot open [Connection refused]
你可以将此作为 if 条件中的 test 使用:
SERVER=127.0.0.1 PORT=8000
if (: < /dev/tcp/$SERVER/$PORT) 2>/dev/null
then
    print succeeded
else
    print failed
fi

如果标准输入重定向失败,无操作将在子shell中执行,因此我可以丢弃标准错误。

我经常使用/dev/tcp来检查HTTP资源的可用性:

W$ print arghhh > grr.html
W$ python -m SimpleHTTPServer &
[1]     16863
Serving HTTP on 0.0.0.0 port 8000 ...
W$ (print -u9 'GET /grr.html HTTP/1.0\n';cat <&9) 9<>/dev/tcp/127.0.0.1/8000
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/2.6.1
Date: Thu, 14 Feb 2013 12:56:29 GMT
Content-type: text/html
Content-Length: 7
Last-Modified: Thu, 14 Feb 2013 12:55:44 GMT

arghhh
W$ 

这个单行命令打开文件描述符9以从套接字读取和写入数据,将HTTP GET发送到套接字并使用cat从套接字读取数据。


1
@A-B-B 我认为你的意思是如果端口没有响应,例如被过滤或者IP上没有任何东西,它会长时间挂起。如果端口主动拒绝连接,关闭的端口不会导致延迟。对于许多目的来说,这是完全可以接受的。 - mc0e
这种技术在此答案之前已经由kenorb先前的答案中发布过。无论如何,更重要的是,如果端口没有响应,它仍然会长时间挂起。例如,尝试 </dev/tcp/canyouseeme.org/80 然后再尝试 </dev/tcp/canyouseeme.org/81 - Asclepius

9

虽然这是一个老问题,但我刚刚遇到了一个变种,但这里的解决方案都不适用,所以我找到了另一种方法,并将其添加到此处供后人参考。是的,我知道原帖作者说他们知道这个选项,但它并不适合他们,但对于后来的任何人来说,它可能会很有用。

在我的情况下,我想要从docker构建中测试本地apt-cacher-ng服务的可用性。这意味着在测试之前绝对不能安装任何东西。没有ncnmapexpecttelnetpython可以使用。但是,perl已经存在,连同核心库,因此我使用了以下内容:

perl -MIO::Socket::INET -e 'exit(! defined( IO::Socket::INET->new("172.17.42.1:3142")))'

7

在某些情况下,如果像curl、telnet、nc或者nmap等工具不可用,你仍然可以试试使用wget。

if [[ $(wget -q -t 1 --spider --dns-timeout 3 --connect-timeout 10  host:port; echo $?) -eq 0 ]]; then echo "OK"; else echo "FAIL"; fi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接