20

grep "^$1" sort of works, but how do I escape "$1" so grep doesn't interpret any characters in it specially?

Or is there a better way?

Edit: I don't want to search for '^$1' but for a dynamically inserted fixed string which should only be matched if it's at the beginning of a line. That's what I meant by the $1.

  • Did you try to use single quotes instead of double quotes, e.g. grep '^$1'? Or didn't you mean that you want to prevent the $1being expanded by the shell? – mnille May 11 '16 at 8:50
  • @mnille I don't want to search for '^$1' but for a dynamically inserted fixed string which should only be matched if it's at the beginning of a line. That's what I meant by the $1. – PSkocik May 11 '16 at 8:53
  • 3
    You can do it with grep too but you'll have to escape any special character in your string first e.g. printf %s ^;printf %s "$1" | sed 's/[][\.*^$]/\\&/g'; } | grep -f- infile – don_crissti May 11 '16 at 16:04
  • @don_crissti that's better than some of the other answers. Care to make it one? – roaima May 14 '16 at 7:36
  • @roaima - I know but there's already a bunch of answers here and this (escaping the special chars inside vars) is something I (and a couple of other users here) have been hammering home for quite some time... You could always add it to your answer if you wish and I'll remove the comment here (don't forget to add the missing the leading brace). – don_crissti May 14 '16 at 17:38
7

I can't think of a way to do this using grep; ^ itself is part of a regular expression so using it requires regular expressions to be interpreted. It's trivial using substring matching in awk, perl or whatever:

awk -v search="$1" 'substr($0, 1, length(search)) == search { print }'

To handle search strings containing \, you can use the same trick as in 123's answer:

search="$1" awk 'substr($0, 1, length(ENVIRON["search"])) == ENVIRON["search"] { print }'
  • This won't work for strings such as \/ – 123 May 11 '16 at 9:18
  • @123 indeed, I've added a variant to handle that. – Stephen Kitt May 11 '16 at 9:24
  • Will still fail for complicated strings such as \\\/\/\/\\\\/ that is seen as \\///\\/ in the program. As far as i am aware there is no way to properly escape backslashes in awk, unless you know how many will be used beforehand. – 123 May 11 '16 at 9:30
  • 1
    @123 thanks, I've adapted your trick of going through the environment to avoid escape processing. – Stephen Kitt May 11 '16 at 9:45
  • I still like this solution the best. Efficient (awk + no time wasted looking around), quick startup (awk + no additional processes needed to setup state) uses standard tools, and is quite concise. All the other answers lack at least some of these. (Efficiency is a strong point here as grep is known for unmatched speed.) – PSkocik May 12 '16 at 21:26
14

If you only need to check whether or not a match is found, cut all input lines to the length of the desired prefix ($1) and then use fixed-pattern grep:

if cut -c 1-"${#1}" | grep -qF "$1"; then
    echo "found"
else
    echo "not found"
fi

It's also easy to get the count of matching lines:

cut -c 1-"${#1}" | grep -cF "$1"

Or the line numbers of all matching lines (line numbers start at 1):

cut -c 1-"${#1}" | grep -nF "$1" | cut -d : -f 1

You could feed the line numbers to head and tail to get the full text of the matching lines, but at that point it's easier to just reach for a modern scripting language like Python or Ruby.

(The above examples assume Posix grep and cut. They assume the file to search comes from standard input, but can easily be adapted to take a filename instead.)

Edit: You should also ensure that the pattern ($1) is not a zero-length string. Otherwise cut fails saying values may not include zero. Also, if using Bash, use set -o pipefail to catch error-exits by cut.

10

A way using perl which will respect backslashes

v="$1" perl -ne 'print if index($_, $ENV{"v"} )==0' file

This sets the environment variable v for the command, then prints if the index of the variable is 0 i.e the beginning of the line.

You can also do identical in awk

v="$1" awk 'index($0, ENVIRON["v"])==1' file
7

Here's an all-bash option, not that I recommend bash for text-processing, but it works.

#!/usr/bin/env bash
# searches for $1 at the beginning of the line of its input

len=${#1}
while IFS= read -r line
do
  [[ "${line:0:len}" = "$1" ]] && printf "%s\n" "$line"
done

The script computes the length len of the inputted parameter $1, then uses parameter expansion on each line to see if the first len characters match $1. If so, it prints the line.

4

If your $1 is pure ASCII and your grep has the -P option (to enable PCRE), you can do this:

#!/bin/bash

line_start="$1"
line_start_raw=$(printf '%s' "$line_start" | od -v -t x1 -An)
line_start_hex=$(printf '\\x%s' $line_start_raw)
grep -P "^$line_start_hex"

The idea here is that grep -P allows regular expressions with \xXX to specify literal characters, where XX is the hex ASCII value of that character. The character is matched literally, even if it is otherwise a special regex character.

od is used to convert the expected line start to a list of hex values, which are then strung together, each prefixed with \x by printf. ^ is then prepended this string to build the required regex.


If your $1 is unicode, then this becomes quite a bit harder, because there is not a 1:1 correspondence of characters to hex bytes as output by od.

3

As a filter:

perl -ne 'BEGIN {$pat = shift} print if /^\Q$pat/' search-pattern

Run on one or more files:

perl -ne 'BEGIN {$pat = shift} print if /^\Q$pat/' search-pattern file..

The “Quoting metacharacters” section of the perlre documentation explains:

Quoting metacharacters

Backslashed metacharacters in Perl are alphanumeric, such as \b, \w, \n. Unlike some other regular expression languages, there are no backslashed symbols that aren’t alphanumeric. So anything that looks like \\, \(, \), \[, \], \{, or \} is always interpreted as a literal character, not a metacharacter. This was once used in a common idiom to disable or quote the special meanings of regular expression metacharacters in a string that you want to use for a pattern. Simply quote all non-“word” characters:

    $pattern =~ s/(\W)/\\$1/g;

(If use locale is set, then this depends on the current locale.) Today it is more common to use the quotemeta function or the \Q metaquoting escape sequence to disable all metacharacters’ special meanings like this:

    /$unquoted\Q$quoted\E$unquoted/

Beware that if you put literal backslashes (those not inside interpolated variables) between \Q and \E, double-quotish backslash interpolation may lead to confusing results. If you need to use literal backslashes within \Q...\E, consult “Gory details of parsing quoted constructs” in perlop.

quotemeta and \Q are fully described in quotemeta.

3

If your grep has the -P option, which means PCRE, you can do this:

grep -P "^\Q$1\E"

Refer to this question, and see PCRE doc for details if you like.

2

If there is a a character that you don't use, you could use that to mark the beginning of the line. For example, $'\a' (ASCII 007). It's ugly but it will work:

{ echo 'this is a line to match'; echo 'but this is not'; } >file.txt

stuffing=$'\a'    # Guaranteed never to appear in your source text
required='this'   # What we want to match that beginning of a line

match=$(sed "s/^/$stuffing/" file.txt | grep -F "$stuffing$required" | sed "s/^$stuffing//")

if [[ -n "$match" ]]
then
    echo "Yay. We have a match: $match"
fi

If you don't need the matched line(s) then you can drop the trailing sed and use grep -qF. But it's much easier with awk (or perl)...

0

When you want to look in a file without a loop you can use:
Cut the file with the length of the search string

  cut -c1-${#1} < file

Look for fixed strings and return line numbers

  grep -Fn "$1" <(cut -c1-${#1} < file)

Use the line numbers for something like sed -n '3p;11p' file

  sed -n "$(grep -Fn "$1" <(cut -c1-${#1} < file) | sed 's/:.*/p;/' | tr -d '\n')" file

When you want to delete these lines, use

  sed "$(grep -Fn "$1" <(cut -c1-${#1} < file) | sed 's/:.*/d;/' | tr -d '\n')" file

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.