229

I've a command that outputs data to stdout (command1 -p=aaa -v=bbb -i=4). The output line can have the following value:

rate (10%) - name: value - 10Kbps

I want to grep that output in order to store that 'rate' (I guess pipe will be useful here). And finally, I would like that rate to be a value of a parameter on a second command (let's say command2 -t=${rate})

It looks to be tricky on my side; I would like to know better how to use pipes, grep, sed and so on.

I've tried lots of combinations like that one but I'm getting confused of these:

$ command1 -p=aaa -v=bbb -i=4 | grep "rate" 2>&1 command2 -t="rate was "${rate}
2
  • Hard to fathom that this has no accepted answer after over 9 years. Commented Feb 20, 2023 at 18:49
  • 2
    @JesseAdelman that's entirely due to the asker failing to select one. Unix & Linux reports they haven't logged in for over nine years so you're unlikely to see one accepted now Commented Apr 25, 2023 at 19:06

7 Answers 7

237

You are confusing two very different types of inputs.

  1. Standard input (stdin)
  2. Command line arguments

These are different, and are useful for different purposes. Some commands can take input in both ways, but they typically use them differently. Take for example the wc command:

  1. Passing input by stdin:

    ls | wc -l
    

    This will count the lines in the output of ls

  2. Passing input by command line arguments:

    wc -l $(ls)
    

    This will count lines in the list of files printed by ls

Completely different things.

To answer your question, it sounds like you want to capture the rate from the output of the first command, and then use the rate as a command line argument for the second command. Here's one way to do that:

rate=$(command1 | sed -ne 's/^rate..\([0-9]*\)%.*/\1/p')
command2 -t "rate was $rate"

Explanation of the sed:

  • The s/pattern/replacement/ command is to replace some pattern
  • The pattern means: the line must start with "rate" (^rate) followed by any two character (..), followed by 0 or more digits, followed by a %, followed by the rest of the text (.*)
  • \1 in the replacement means the content of the first expression captured within \(...\), so in this case the digits before the % sign
  • The -n flag of the sed command means to not print lines by default. The p at the end of the s/// command means to print the line if there was a replacement. In short, the command will print something only if there was a match.
5
  • No, it would be useful to have what OP asked - many times we don't get to control how a tool passes its output or accepts its input. Some decided via stdin/out, some uses arguments. It'd be very useful to have a way to glue those different tools together.
    – KFL
    Commented Mar 22, 2022 at 19:00
  • @KFL see my answer gluing together with pipes and xargs Commented Jun 2, 2022 at 17:17
  • What about this answer doesn't answer the original question? It feels like, even if it has more info than was directly requested, gives good options to ponder. Commented Feb 20, 2023 at 18:50
  • @JesseAdelman It's a great answer, but I think KFL wanted to see the answer explicitly pipe the two into a single command Commented Apr 25, 2023 at 18:17
  • @MichaelAnderson Yah. It seems a shame to have someone ask a question like "How can I build a house made of straw, but made of bricks?", and get good answers about building a house with bricks, but get the answerer's unrewarded for their answers. That said, I don't claim to know how to resolve that situation... Commented Apr 25, 2023 at 21:52
106

I tend to use this:

command1 | xargs -I{} command2 {}

Pass output of command1 through xargs using substitution (the braces) to command2. If command1 is find be sure to use -print0 and add -0 to xargs for null terminated strings and xargs will call command2 for each thing found.

In your case (and taking the sed line from @janos):

command1 -p=aaa -v=bbb -i=4 | sed -ne 's/^rate..\([0-9]*\)%.*/\1/p' | xargs -I{} command2 -t="rate was {}"
0
6

I generally use `command` to place it's output as argument to another command. For e.g., to find resource consumed by process foo on freebsd will be:

procstat -r `pgrep -x foo`

Here, pgrep is used to extract the PID of process foo which is passed to procstat command which expects PID of process as argument.

5

To simulate the output of command1 I am using this echo statement:

$ echo -e "Foo\nrate (10%) - name: value - 10Kbps\nBar"
$ alias command1='echo -e "Blah\nrate (10%) - name: value - 10Kbps\nBlag"'

A quick test:

$ command1
Blah
rate (10%) - name: value - 10Kbps
Blag

Thats all good, so lets parse it out:

$ command1 | grep 'rate'
rate (10%) - name: value - 10Kbps

So we get the line we want out of command1, lets pass that into command2:

$ alias command2='echo'
$ command2 -t="rate was "$(command1 | grep 'rate')
-t=rate was rate (10%) - name: value - 10Kbps

I'm expecting "rate was "$(command1 | grep 'rate') to concatenate automatically. If that doesn't work because of whitespace, you should be able to pass the input like so instead:

$ alias command2='echo'
$ command2 -t=$(echo '"rate was ' $(command1 | grep 'rate') '"')
-t="rate was rate (10%) - name: value - 10Kbps "
3

Try this using & :

command | grep -oP '\$>\s+rate\s+\(\K[^\)]+'
1
  • 4
    So grep will just output some value but the question is how to use that value as a parameter of another command. Unfortunately, that important part is missing in your answer. Commented Oct 31, 2019 at 17:05
3

The first task is to extract the rate from that line. With GNU grep (non-embedded Linux or Cygwin), you can use the -o option. The part you want is the one containing only digits, and followed by a % sign. If you don't want to extract the % itself, you need an additional trick: a zero-width lookahead assertion, which matches nothing but only if that nothing is followed by %.

command1 -p=aaa -v=bbb -i=4 | grep -o -P '[0-9]+(?=%)'

Another possibility is to use sed. To extract a part of a line in sed, use the s command, with a regex that matches the whole line (starting with ^ and ending with $), with the part to retain in a group (\(…\)). Replace the whole line by the content of the group(s) to keep. In general, pass the -n option to turn off default printing and put the p modifier to print lines where there is something to extract (here there's a single line so it doesn't matter). See Return only the portion of a line after a matching pattern and Extracting a regex matched with 'sed' without printing the surrounding characters for more sed tricks.

command1 -p=aaa -v=bbb -i=4 | sed 's/^.*rate(\([0-9]*\)%).*$/\1/'

More flexible again than sed, is awk. Awk executes instructions for each line in a small imperative language. There are many ways to extract the rate here; I select the second fields (fields are delimited by whitespace by default), and remove all the characters in it that are not a digit.

command1 -p=aaa -v=bbb -i=4 | awk '{gsub(/[^0-9]+/, "", $2); print $2}'

The next step, now that you've extracted the rate, is to pass it as an argument to command2. The tool for that is a command susbtitution. If you put a command inside $(…) (dollar-parenthesis), its output is substituted into the command line. The output of the command is split into separate words at each whitespace block, and each word is treated as a wildcard pattern; unless you want this to happen, put double quotes around the command substitution: "$(…)". With the double quotes, the output of the command is used directly as a single parameter (the only transformation is that newlines at the end of the output are removed).

command2 -t "$(command1 -p=aaa -v=bbb -i=4 |
               sed 's/^.*rate(\([0-9]*\)%).*$/\1/')"
1

You can use grep and it's PCRE - Perl Compatible Regular Expressions. This allows you to utilize a lookbehind to match the value of rate, without inclusion of the string "rate" within your results when grepping for it.

Example

$ echo "rate (10%) - name: value - 10Kbps" | grep -oP '(?<=^rate \()\d+'
10

Details

The above grep works as follows:

  • -o will return only what we're looking for, \d+, which is the digits within the parens.
  • -P enables grep's PCRE feature
  • (?<=^rate \() will only return strings that begin with "rate ("

command2

To catch the value of "rate" you can run command1 like so:

$ rate=$(command 1 | grep -oP '(?<=^rate \()\d+'

Then for your 2nd command you'd simply use that variable.

$ command2 -t=${rate}

You could get fancy and make that all one line:

$ command2 -t=$(command1 | grep -oP '(?<=^rate \()\d+')

This will execute command1 inside the $(..) execution block, take its results and include them in command2's -t=.. switch.

1
  • ... assuming it supports that option of course. It's not required by POSIX and not all greps have -E. But if it does it's a great feature of course.
    – Pryftan
    Commented Mar 8, 2023 at 16:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .