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
循环。
此外,您根本不需要使用 awk
、grep
或 sed
(尽管 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
我使用以下命令输出与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
循环。
此外,您根本不需要使用 awk
、grep
或 sed
(尽管 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