5

I need to find out what is the line number of first occurrence of a given search string that should be in the start of a line in a text file and store it in a variable in my bash script. For example I want to find the first occurrence of "c":

abc
bde
cddefefef // this is the line that I need its line number
Casdasd // C here is capital, I dont need it
azczxczxc
b223r23r2fe
Cssdfsdfsdf
dccccdcdcCCDcdccCCC
eCCCCCC

I came up with this but as you see there are big problems

   trimLineNum=$(cat "${varFileLog}" | grep -m1 -n "c")
   echo "c is at line #"${trimLineNum}

The output will be:

c is at line #1:abc

Problems:

  1. So obviously it matches the first line, because there is a "c" in the line.
  2. The output will also include the content of the line as well! I want it to be just the number of the line

what should I change to fix those problems?

5 Answers 5

8

With POSIX sed, you suppress normal output with the -n option, then for the line starting with c (pattern ^c), print the line number with = and quit:

sed -n '/^c/{=;q;}'

With GNU sed, you can use the Q command to quit without output and simplify to

sed '/^c/!d;=;Q'
3
  • You could simplify to sed '/^c/=;d' (no -n option used). Of course, that will run the whole file tru sed. Jun 24, 2022 at 11:05
  • @QuartzCristal This will work for the example file, but the description says to only print the line number of the first occurence, so it would require an additional |head -n 1 or |sed 1q.
    – Philippos
    Jun 27, 2022 at 4:59
  • Yes, that's correct. That is why I said that will run the whole file tru sed. Jun 27, 2022 at 12:52
7

Several solutions exist

with AWK

awk '/^c/ { print NR; exit}' "${varFileLog}"
  • /^c/: matches the line starting with c
  • print NR: prints the record (line) number
  • exit : does not continue processing

As I like awk, this is my preferred solution

with grep + filtering

grep -n '^c' "${varFileLog}" | head -n1 | sed 's/:.*//'
  • '^c': matches the line starting with c
  • head -1 : only displays first line from grep's results
  • sed 's/:.*//' : removes anything after the :

sed 's/:.*//' and cut -d: -f1 have the same effect in that case

about performance

This may be slower than Stephen's solution:

grep -m1 -n '^c' "${varFileLog}" | cut -d: -f1
4
  • upvoted for the awk answer. I wouldn't bother with the rest as they're all inferior to that one in terms of conciseness and/or portability and/or efficiency.
    – Ed Morton
    Jun 27, 2022 at 12:15
  • Why pass grep output to cut? If the result need to be saved in a variable i think is better to use parameters expansion: lineno=$(grep -m1 -n -- '^c' "${varFileLog}");lineno="${lineno%%:*}" Jul 1, 2022 at 14:40
  • 1
    Why pipe to tail? The OP need the first so it should be head. Also note that ... | head -1 | sed '...' can be done with just sed as well : ... | sed '1s/:.*//;q' Jul 1, 2022 at 17:15
  • @DanieleGrassini that is correct, thank you, I corrected my answer
    – lauhub
    Jul 2, 2022 at 19:32
4

You need to tell grep about your “that should be in the start of a line” constraint, by anchoring the match to the start of a line with ^:

trimLineNum=$(grep -m1 -n -- '^c' "${varFileLog}")

Then post-process grep’s output to only keep the line number:

trimLineNum=$(grep -m1 -n -- '^c' "${varFileLog}")
trimLineNum="${trimLineNum%%:*}"

Note that -m is a GNU extension (and with GNU grep, you need -- even though ^c doesn't start with -- in case $varFileLog itself might start with - as GNU grep accepts options even after non-option arguments). Standardly, you could pipe the output to head -n 1 instead.

If there's no match, the first command will return false/failure while the second will always return true unless you enable the pipefail option as supported by several shells including bash.

0
0

grep can print the line number of a match with -n or --line-number so you can use that.

$ cat sample.txt | grep '^c' --line-number
3:cddefefef // this is the line that I need its line number
10:cat // added to illustrate getting the first occurence vs. all

The problem then reduces to:

  • Get the first line only
  • Pull out just the number without the matched text

You can do the first one with head and the second with cut:

$ cat sample.txt | grep '^c' --line-number | head -n 1 | cut -d':' -f1
3

In your example output, you have some extra text - I'm not sure if this is important to you or not. However, when you have a number on STDOUT, adding some string prefix to this is a straightforward task, and I'll leave that one up to you.

1
  • 2
    Note that in grep '^c' --line-number, POSIX compliant implementations of grep are required to print the lines of the --line-number file that start with c. GNU grep only does that if called with POSIXLY_CORRECT in its environment. You should put options before non-option arguments. POSIXly: <sample.txt grep -n '^c' (also removing the UUOC and replacing the --line-number GNUism with the standard and portable -n). Jun 25, 2022 at 14:45
0

Using Raku (formerly known as Perl_6)

raku -ne 'state $i; ++$i; say "c starts line $i" and last if m/^c/;'  

OR

raku -ne 'state $i; ++$i; say "c starts line $i" and last if (.index("c").defined && .index("c") == 0);' 

OR

raku -ne 'state $i; ++$i; say "c starts line $i" and last if .starts-with("c");' 

Outputs:

c starts line 3

Raku's -ne (linewise non-autoprinting) command line flags are used. To get the line number a state variable $iis initialized once, then incremented for every line read. Where a beginning-of-line "c" is identified (either by regex, or index, or starts-with), the string "c starts line $i" is interpolated and output (say).

Note: the low-precedence conditional as last is added to each example above. Remove this conditional to return all matching line numbers, e.g.:

~$ raku -ne 'state $i; ++$i; say "c starts line $i" if m/^c/;'  file
c starts line 3
c starts line 10

Addendum: Thanks to this SO answer, here's a quick way to get the first zero-indexed line number starting with "c" using Raku's first routine:

~$ raku -e 'say lines.first(* ~~ / ^ c /):k;' file
2

#OR

~$ perl6 -e 'say lines.first(*.starts-with("c")):k;'  file
2

Sample Input:

abc
bde
cddefefef // this is the line that I need its line number
Casdasd // C here is capital, I dont need it
azczxczxc
b223r23r2fe
Cssdfsdfsdf
dccccdcdcCCDcdccCCC
eCCCCCC
cddefefef // this is the line that I need its line number (again)

https://raku.org

You must log in to answer this question.

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