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

9

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. Commented 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
    Commented Jun 27, 2022 at 4:59
  • Yes, that's correct. That is why I said that will run the whole file tru sed. Commented 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
    Commented 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%%:*}" Commented 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' Commented Jul 1, 2022 at 17:15
  • @DanieleGrassini that is correct, thank you, I corrected my answer
    – lauhub
    Commented 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). Commented 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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.