【问题标题】:Writing bash code for performance standards为性能标准编写 bash 代码
【发布时间】:2012-10-05 15:06:33
【问题描述】:

是否有更好的方法来重写此代码以提高性能?

如果您要获得一堆 IP,系统似乎会挂起。

TMP_PREFIX='/tmp/synd'
TMP_FILE="mktemp $TMP_PREFIX.XXXXXXXX"
BANNED_IP_MAIL=`$TMP_FILE`
BANNED_IP_LIST=`$TMP_FILE`
echo "Banned the following ip addresses on `date`" > $BANNED_IP_MAIL
echo >> $BANNED_IP_MAIL
BAD_IP_LIST=`$TMP_FILE`
netstat -ntu | grep SYN_RECV | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr > $BAD_IP_LIST
cat $BAD_IP_LIST
if [ $KILL -eq 1 ]; then
    IP_BAN_NOW=0
    while read line; do
        CURR_LINE_CONN=$(echo $line | cut -d" " -f1)
        CURR_LINE_IP=$(echo $line | cut -d" " -f2)
        if [ $CURR_LINE_CONN -lt $NO_OF_CONNECTIONS ]; then
            break
        fi
        IGNORE_BAN=`grep -c $CURR_LINE_IP $IGNORE_IP_LIST`
        if [ $IGNORE_BAN -ge 1 ]; then
            continue
        fi
        IP_BAN_NOW=1
        echo "$CURR_LINE_IP with $CURR_LINE_CONN SYN_RECV connections" >> $BANNED_IP_MAIL
        echo $CURR_LINE_IP >> $BANNED_IP_LIST
        echo $CURR_LINE_IP >> $IGNORE_IP_LIST
        if [ $CSF_BAN -eq 1 ]; then
            $CSF -d $CURR_LINE_IP
        else
            $IPT -I INPUT -s $CURR_LINE_IP -j DROP
        fi
    done < $BAD_IP_LIST
    if [ $IP_BAN_NOW -eq 1 ]; then
        dt=`date`
                hn=`hostname`
        if [ $EMAIL_TO != "" ]; then
            cat $BANNED_IP_MAIL | mail -s "IP addresses banned on $dt $hn" $EMAIL_TO
        fi
    fi
fi
rm -f $TMP_PREFIX.*

【问题讨论】:

  • 如果性能真的很重要,那么按照定义解释 Bash 可能不是正确的工具(我猜 Lua、Ocaml 或 Python 脚本可能运行得更快,特别是因为它被编译为某些字节码)。
  • 如果你有大数据要处理或者你有一堆阻塞进程要管理,Shell 在性能上非常方便。因为它有最原生的进程控制机制,只要你知道你想要什么。

标签: linux bash shell iptables netstat


【解决方案1】:

当然,有很多方法可以改进,但您应该尝试找出真正的瓶颈在哪里。 (很可能是 iptables,在这种情况下,您可能希望尝试在一次调用中完成所有表更新,而不是一次一次。但我只是在猜测。)

这里有一些建议;我没有通读一遍:

netstat -ntu | grep SYN_RECV | awk '{print $5}' | cut -d: -f1 |
sort | uniq -c | sort -nr > $BAD_IP_LIST

如果您只对 SYN_RECV 状态的连接感兴趣,为什么要列出 udp?无论如何,您正在使用三个实用程序(grepawkcut)来执行一项简单的面向行的操作。您不妨一气呵成,例如 awk:

awk '$6 == "SYN_RECV" {print substr($5, 1, index($5, ":") - 1)}'

事实上,你也可以在 awk 中进行唯一性和计数:

awk '$6 == "SYN_RECV" {++ip[substr($5, 1, index($5, ":") - 1)]} END{for (i in ip) print ip[i], i}'

编辑:您也可以在此处按所需计数进行过滤:

awk '$6 == "SYN_RECV" {++ip[substr($5, 1, index($5, ":") - 1)]}
     END              {for (i in ip) if (ip[i] >= '$NO_OF_CONNECTIONS') print ip[i], i}'

现在你只需要输出 ip 地址,因为你不再需要在 bash 脚本中过滤。我不知道这是否比通过排序和 uniq 再排序更快,但很可能是这样。

while read line; do
    CURR_LINE_CONN=$(echo $line | cut -d" " -f1)
    CURR_LINE_IP=$(echo $line | cut -d" " -f2)
    if [ $CURR_LINE_CONN -lt $NO_OF_CONNECTIONS ]; then
        break
    fi

您想从标准输入读取两个字段。你为什么不这样做:

while read CURR_LINE_CONN CURR_LINE_IP IGNORED &&
      ((CURR_LINE_CONN >= NO_OF_CONNECTIONS)); do

这节省了两个子外壳和两个剪切调用。 (read built-in 中的 IGNORED 只是妄想症,因为 awk 只会输出两个字段。不过,这不是好的妄想症,因为它会默默地忽略错误。)

编辑:如上所述,您也可以在这里摆脱测试。所以它只是:

netstat -nt |
awk '$6 == "SYN_RECV" {++ip[substr($5, 1, index($5, ":") - 1)]}
     END { for (i in ip)
             if (ip[i] >= '$NO_OF_CONNECTIONS')
               print ip[i], i}' | tee $BAD_IP_LIST
if ((KILL)); then
  IP_BAN_NOW=0
  while read IP IGNORED; do

下一步:

IGNORE_BAN=`grep -c $CURR_LINE_IP $IGNORE_IP_LIST`
    if [ $IGNORE_BAN -ge 1 ]; then
        continue
    fi

grep -c 让 grep 读取整个输入文件以获取计数;你只想知道ip是否存在。你要grep -q:

if $(grep -q -F -x $CURR_LINE_IP $IGNORE_IP_LIST); then continue; fi

(-F 告诉 grep 将模式解释为字符串而不是正则表达式,这是您想要的,因为否则 . 是通配符。-x 告诉 grep 匹配整行。一个 ip 可能成为另一个前缀或后缀甚至是中缀,这将导致错误匹配。-F 和 -x 的组合也可能更快一些,因为 grep 可以优化匹配。)

可能还有更多。就我所知。

【讨论】:

  • 每个使用 cut、grep、sed 管道的 awk 都低估了它可以做什么。准备好了
猜你喜欢
  • 1970-01-01
  • 2010-10-28
  • 1970-01-01
  • 1970-01-01
  • 2018-05-24
  • 1970-01-01
  • 1970-01-01
  • 2010-10-21
  • 1970-01-01
相关资源
最近更新 更多