0

Question 1

I can use

grep -o '^[[:alpha:]_]\+[[:blank:]]*([[:blank:]]*)' /etc/rc.d/init.d/functions

to get output like below:

systemctl_redirect ()
checkpid()
__kill_pids_term_kill_checkpids()
__kill_pids_term_kill()
__pids_var_run()
__pids_pidof()
daemon()
killproc()
pidfileofproc()
pidofproc()
status()
echo_success()
echo_failure()
echo_passed()
echo_warning()
update_boot_stage()
success()
failure()
passed()
warning()
action()
strstr()
is_ignored_file()
is_true()
is_false()
apply_sysctl()

And I also want to know how many occurrences had been matched, so I use -c option, this time I only get 26, can I combine matched contents and counts with grep built-in options? If not, how to?

Question 2

I found a solution on Github to answer Question 1:

grep -o '^[[:alpha:]_]\+[[:blank:]]*([[:blank:]]*)' /etc/rc.d/init.d/functions \
| tee >(echo -e "\n`wc -l` matched.")

But the output is often weird, which is output after a new shell prompt! Why?

output after a new shell prompt

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
liyang
  • 43

2 Answers2

3

You can always do:

grep -o ... | awk '{print};END{if (NR) print "\n" NR " matched."}'

Or do the whole thing in awk (which would also avoid the -o and \+ GNUisms):

awk 'match($0, /^[[:alpha:]_]+[[:blank:]]*\([[:blank:]]*\)/) {
       print substr($0, RSTART, RLENGTH)
       n++
     }
     END{if (n) print "\n" n " matched.")'

or perl:

perl -lne 'for (/^\w+\h*\(\h*\)/g) {print; $n++}
           END {print "\n$n matched." if $n}'

(note that in that case the \w is limited to ASCII letters, add a -Mopen=locale to include any letter in any alphabetical script according to locale like in the grep or awk (some awk) approaches)

About your question 2, that's because bash (contrary to zsh) doesn't wait for commands started in process substitution causing this kind of problem. See The process substitution output is out of the order for more details.

0

Instead of tee use a tool like pee:

grep -o '^[[:alpha:]_]\+[[:blank:]]*([[:blank:]]*)' /etc/rc.d/init.d/functions | 
pee cat 'sleep 1; echo -e "\n`wc -l` matched."'

If pee isn't available, it can be done with a separate bash shell, and silence its prompts with unset and shopt:

bash -c \
    "unset PS0 PS1 PS2 PS3
     shopt -u promptvars
     grep -o '^[[:alpha:]_]\+[[:blank:]]*([[:blank:]]*)' \
          /etc/rc.d/init.d/functions |
     tee >( sleep 1s; printf '\n%s matched.' `wc -l`; )
     sleep 2s"

General answers:

  1. Can't be done with a single instance of grep.

    To get a full count when there's more than one match per line with grep -o, use wc -l:

    printf "foo bar baz\nbuz biz\n" | grep -o 'b[^ ]*' | wc -l
    

    Output:

    4
    

    If a prefixed count is needed, use nl, (or cat -n):

    printf "foo bar baz\nbuz biz\n" | grep -o 'b[^ ]*' | nl
    

    Output:

         1  bar
         2  baz
         3  buz
         4  biz
    
  2. With parallel processes, i.e.

    tee >(echo -e "\n`wc -l` matched.")
    

    there's no guarantee which process finishes first. Sometimes a little delay can be added to insure the order.

    Print parallel "foo" first, then "oof" after a 1 second delay:

    echo foo | tee >(sleep 1s; rev)
    

    Print parallel "oof" first, then "foo" after a 1 second delay:

    echo foo | tee >(rev) >(sleep 1s;cat) > /dev/null
    
agc
  • 7,223
  • Thanks for your explanation, but tee >(sleep 1s; echo -e "\n`wc -l` matched.") can't solve my problem. – liyang Nov 21 '17 at 09:39
  • @liyang, Please provide more detail on what sort of solution is required. Specifically, what should the output look like? – agc Nov 21 '17 at 09:45
  • The output should like this: https://i.imgur.com/MdwPeBN.png. My problem is: the last line 26 matched. was output at correct place this time, but will be placed after a new shell prompt next time, and I want to control it. – liyang Nov 21 '17 at 10:38