bash 输出字符串中有额外的换行符

bash output with extra newline within the string

我使用以下命令输出与CLOSE-WAIT套接字相关的PID和FD。

sudo ss -p | grep CLOSE-WAIT | awk '{ print  }' | sed 's/,pid=/ /' | sed 's/,fd=/ /' | sed 's/))//' | awk '{ print ,  }'

PID FD 对采用如下一行时正确输出。

22487 27
22652 21
21846 33
21780 26
22476 14
21571 10
22740 17
21901 26

但是如果我使用 for 循环,它的输出会有所不同。

#!/bin/bash


for PidFd in `sudo ss -p | grep CLOSE-WAIT | awk '{ print  }' | sed 's/,pid=/ /' | sed 's/,fd=/ /' | sed 's/))//' | awk '{ print ,  }'`
do
    echo "$PidFd"
done

它在两者之间添加了额外的换行符。

22487
27
22652
21
21846
33
21780
26
22476
14
21571
10
22740
17
21901
26

我想修复它以获得相同的输出。

你会得到一个额外的换行符,因为 for 不只是按换行符拆分——它还会按空格拆分,所以不是将 pid/fd 对分配给 PidFd,它 first 分配了一个 PID,then 分配了一个 FD。正如 DontReadLinesWithFor 中广泛记录的那样,best-practice 方法是使用 while read 循环。

此外,您根本不需要使用 awkgrepsed(尽管 grep可以在具有大量套接字的系统上提高性能,但代价是在只有少量套接字的系统上性能更差):

#!/usr/bin/env bash
case $BASH_VERSION in '') echo "ERROR: Your shell must be bash, not sh" >&2; exit 1;; esac

pid_re='pid=([[:digit:]]+)($|[^[:digit:]])'
fd_re='fd=([[:digit:]]+)($|[^[:digit:]])'
while IFS= read -r line; do pid=; fd=
  [[ $line =~ $pid_re ]] && pid=${BASH_REMATCH[1]}
  [[ $line =~ $fd_re ]] && fd=${BASH_REMATCH[1]}
  [[ $pid && $fd ]] && echo "$pid $fd"
done < <(sudo ss -p | grep CLOSE-WAIT)

正如 Charles 评论的那样,awk 可以完成整个管道

sudo ss -p | awk '
    /CLOSE-WAIT/ {
        field = 
        sub(/,pid=/, " ", field)
        sub(/,fd=/, " ", field)
        sub(/))/, "", field)
        split(field, a)
        print a[2], a[3]
    }
'

bashmapfile命令可以将读入数组:

mapfile -t lines < <(
    sudo ss -p | awk '
        /CLOSE-WAIT/ {
            field = 
            sub(/,pid=/, " ", field)
            sub(/,fd=/, " ", field)
            sub(/))/, "", field)
            split(field, a)
            print a[2], a[3]
        }
    '
)

现在,你可以做到

for PidFd in "${lines[@]}"; do
    echo "$PidFd"
done