Home > Mobile >  bash output with extra newline within the string
bash output with extra newline within the string

Time:05-14

I use the following command to output PID and FD related to CLOSE-WAIT sockets.

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

It outputs correctly where a PID FD pair takes one line like below.

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

But if I use the for loop it outputs differently.

#!/bin/bash


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

It adds extra newline between the two.

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

I would like to fix that to have the same output.

CodePudding user response:

You get an extra newline because for doesn't just split on newlines -- it also splits on spaces, so instead of getting a pid/fd pair assigned to PidFd, it first has a PID assigned, and then has a FD. As extensively documented at DontReadLinesWithFor, the best-practice approach is to use a while read loop instead.

Moreover, one doesn't need to use awk, grep, or sed at all for what you're trying to accomplish here (although grep can help performance on systems with a large number of sockets, at the expense of making it worse on systems with only a small number of sockets):

#!/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)

CodePudding user response:

As Charles commented, awk can do the whole pipeline

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

The bash mapfile command can read lines into an array:

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

Now, you can do

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