0

I have a script that parses a command:

while read line; do
        # The third token is either IP or protocol name with '['
        token=`echo $line | awk '{print $3}'`
        last_char_idx=$((${#token}-1))
        last_char=${token:$last_char_idx:1}
        # Case 1: It is the protocol name
        if [[ "$last_char" = "[" ]]; then
                # This is a protocol. Therefore, port is token 4
                port=`echo $line | awk '{print $4}'`
                # Shave off the last character
                port=${port::-1}
        else
                # token is ip:port. Awk out the port
                port=`echo $token | awk -F: '{print $2}'`
        fi
        PORTS+=("$port")
done < <($COMMAND | egrep "^TCP open")

for p in "${PORTS[@]}"; do
        echo -n "$p, "
done

This prints out ports like:

80,443,8080,

The problem is that trailing slash ,

How can I get the last port to not have a trailing , in the output ?

Thanks

Jshee
  • 2,620
  • 6
  • 44
  • 60
  • Doesn't answer your immediate question, but it would be much more efficient if you changed `read line` to `read f1 f2 token f4 _` -- no more `awk` needed if you let `read` do the splitting, and then you can assign `port` from either `$f4` or `$f2`depending on the test. – Charles Duffy Dec 19 '18 at 21:29
  • Thanks Charles. Can you help with the request though? – Jshee Dec 19 '18 at 21:30
  • ...as another aside, `$COMMAND | ...` is quite buggy; see [BashFAQ #50](http://mywiki.wooledge.org/BashFAQ/050) describing why, and various better-behaved alternatives. You also might consider using lower-case names for your own variables -- all-caps names are used for variables meaningful to the shell and operating system's tools, as specified by POSIX @ http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html – Charles Duffy Dec 19 '18 at 21:34
  • How about `echo -n "${array[0]}"; unset array[0]; for p in "${array[@]}"; do echo -n ", $p"; done`? – Stefan Hamcke Dec 19 '18 at 21:42
  • 1
    @StefanHamcke, that might as well be `printf '%s' "${array[0]}"; printf ',%s' "${array[@]:1}"`; no need for any `echo`s, explicit looping, nor changing the array. That said, it's a good approach, and if you don't mind I'll fold it into my answer. – Charles Duffy Dec 19 '18 at 21:43
  • @CharlesDuffy: Yes, `${array[@]:1}` together with `printf` works! I'm still learning bash, though I'm learning new things every day by playing around with it. Feel free to use my approach in your answer. – Stefan Hamcke Dec 19 '18 at 21:55

3 Answers3

3

${array[*]} uses the first character in IFS to join elements.

IFS=,
echo "${PORTS[*]}"

If you don't want to change IFS, you can instead use:

printf -v ports_str '%s,' "${PORTS[@]}"
echo "${ports_str%,}"

...or, simplified from a suggestion by Stefan Hamcke:

printf '%s' "${PORTS[0]}"; printf ',%s' "${PORTS[@]:1}"

...changing the echo to printf '%s' "${ports_str%,}" if you don't want a trailing newline after the last port. (echo -n is not recommended; see discussion in the APPLICATION USAGE of the POSIX spec for echo).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
1

how about

$ echo "${ports[@]}" | tr ' ' ','
karakfa
  • 66,216
  • 7
  • 41
  • 56
0

Why not simply:

( for p in "${PORTS[@]}"; do
    echo -n "$p, "
  done ) | sed -e 's/,$//'
  • That's fairly inefficient -- the parens create a subshell (so `fork()`ing off a new process), the pipeline requires another layer of subprocess creation, and then the use of `sed` means we're `fork()`ing yet another subprocess only to use the `execve()` syscall to replace it with an external program. – Charles Duffy Dec 19 '18 at 21:33
  • I am a bit puzzled by this comment. Was sub-millisecond efficiency something that was looked for? This solution is simple, even if it is probably not the most efficient. Other solutions are certainly also worthwhile. –  Dec 19 '18 at 21:50
  • Shell scripts have a reputation of being slow -- in some quarters being described as too slow even to be suitable for the traditional role of starting services at boot time. Part of *why* they're considered slow is that they often *are* orders of magnitude slower (literally, ~100x slower) than they need to be due to widespread use of extremely inefficient practices. Teaching good practices whether or not they're immediately relevant means more people will be using those practices, so more scripts will be efficiently written in practice. – Charles Duffy Dec 19 '18 at 21:53
  • Running this 100 times in a loop with output to /dev/null, I get 0.425s wall-clock time; for the two-printf formulation based on Stefan's comment on the question, it's 0.005s for all 100 runs. – Charles Duffy Dec 19 '18 at 21:55
  • Simplicity, and being easy to understand and maintain are also important aims. Not all scripts need to be optimized to be hyper-efficient. Gaining 0.001 second on an utility script might not always be worthwhile. –  Dec 19 '18 at 22:01
  • One pattern is only more "easy to understand and maintain" than the other because you already know it. Since we here are the folks building a teaching resource, what it is people know -- and thus what it is people find easy -- is within our influence. By contrast, if you want to influence what's *efficient*, you need to be writing code for a widely-used shell -- much more work, and ksh93 is already pretty much pushing the limits of what efficiency is feasible while staying POSIX-compliant. – Charles Duffy Dec 19 '18 at 22:39
  • (Rich Hickey's "simple vs easy" talk is a good place to start in terms of having shared vocabulary to use in discussion here. I'd argue that the differences between the practices discussed is one of *ease* -- which practices are at hand and readily recognized -- not *simplicity* -- which practices have less innate complexity in terms of entwined interests; indeed, in bash, I'd argue that without regard to what's *easy*, using builtins is almost always more *simple*, since it doesn't require dependencies on OS-vendor-provided tools, which bring in portability concerns). – Charles Duffy Dec 19 '18 at 22:42
  • [BashPitfalls](http://mywiki.wooledge.org/BashPitfalls) is full of examples of practices that are easy to remember and recognize but subtly buggy. It certainly can be taken as an indictment of the language that doing the obvious thing is so often slow and/or broken, but that doesn't change the reality that doing the easy thing in shell is often *wrong*, and writing robust code requires spending time and effort on learning a set of robust best practices. – Charles Duffy Dec 19 '18 at 22:49
  • ...to pull out an example from the code in this answer: `echo -n` isn't guaranteed not to literally print `-n` on output. Even bash sometimes behaves that way, when configured to be strictly POSIX-compliant (configuration which can be enabled *either* at compile-time or runtime); try running `set -o posix; shopt -s xpg_echo; echo -n hello` (and see the APPLICATION USAGE and RATIONALE sections of [the POSIX spec for `echo`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html)) – Charles Duffy Dec 19 '18 at 22:53
  • Please explain how my proposal is subtly buggy. Or not robust. –  Dec 19 '18 at 22:54
  • See the immediately-preceding comment. – Charles Duffy Dec 19 '18 at 22:54
  • The "echo -n" was part of the original script. I did not change it. I tried to offer a simple solution to the question that was asked. Sorry if that was not the proper teaching resource you were looking for. –  Dec 19 '18 at 23:03