user299709
user299709

Reputation: 5412

Bash: how to wait for a command to finish running to parse out a string?

I'm writing a bash script that needs to let npm run server finish running which will output an ID number that I need

npm run server outputs

running server...
....
...
...
Server version is /id/1345

I need to parse out the 1345 after the command finishes running.

I've attempted the following but without success:

idd=$(npm run server "$domain" &> >(sed -E -e 's:(^.*)(id/)(.*$):\3:g'))
echo $idd

which throws:

run.sh: command substitution: line 27: syntax error near unexpected token `>'

I've also tried:

bundle_counter=$(npm run server "$domain" | sed -E -e 's:(^.*)(/b/)(.*$):\3:g')

which fails.

Upvotes: 1

Views: 787

Answers (3)

mklement0
mklement0

Reputation: 437803

The immediate problem:

The error message indicates that you're invoking Bash as sh (or at least in POSIX-compatibility mode), in which case process substitutions (<(...) and >(...)) aren't supported, because they're not part of POSIX.

If your sed command worked in principle, it would suffice to ensure that your command is run by bash - invoked by that name, using shebang line #!/bin/bash.

That said, using an output process substitution is needlessly complicated.

The problem with your sed command:

Your sed command doesn't work as intended, because, in the absence of option -n, sed still prints all (potentially modified) input lines, not just the (modified) line of interest.
Thus, any line that your s (substitution) command's regular expression doesn't match, is still printed, as-is.

See Ewan Mellor's helpful answer for a working solution.

Upvotes: 1

Midgard
Midgard

Reputation: 402

idd=$(npm run server "$domain" | sed -n -E '$s+id/(.*)$+\1+p')

I piped npm's output to sed, like you did in your second snippet. The -E flag, which you also used, enables extended regexes. Those aren't even needed here now that I think of it, but it's kind of standard because EREs are more useful.

-n tells sed to by default not print its buffer when going to the next line. It does more or less the same as when you'd use d on every input line after all other commands. When using -n you have to explicitly ask sed to print certain lines, with the p command – but that's actually not used here, as we'll shortly see!

The $ will make sure that the command is only executed on the last line of the input.

The s command is the replacement command. You can use any character right after it and that will be s's delimiter. The classic is / but here I've used + since we are using a / in the needle and this way we don't have to escape the /. id/(.*)$ is the needle and \1 is the replacement. You don't have to anchor your strings like you do, so a needle of ^(.*)id/(.*)$ and corresponding replacement \2 is equivalent but redundant (and will most likely impose a slight performance overhead).

Because there's no semicolon after s, p is actually not a separate command here, but a flag to s. It will print the buffer iff the s command was successful in finding something to replace.

Upvotes: 2

Ewan Mellor
Ewan Mellor

Reputation: 6847

idd=$(npm run server "$domain" 2>&1 | sed -E -ne 's:(^.*)(id/)(.*$):\3:p')
  • 2>&1 to merge stderr onto stdout.
  • A simple pipe to pass the output to sed.
  • -n on the sed command to not print any non-matching lines.
  • p at the end of the regex to print the match.

Upvotes: 2

Related Questions