2

I have a script that takes a string input from users. I am looking to check that the string input should have exactly 2 dots. The relevance is only to the dots. The string should not start and end with a dot.There should be no consecutive dots.

This is the pattern I am using:

^[^\.]*\.[^\.]*\.[^\.]*$

This is the string I am looking for:

abc.def.xyz

But in the pattern above, if dots are in front or at the end, then that string gets selected - which I don't want. There should be only two dots in the string.

Not wanted:

.abc.xyz # no dot at the start   
abc.xyz. # no dot at the end   
abc.def.ced.xyz # only two dots not more than that

I have tried using (?!\.) for the dot at the start, but it didn't work.

3
  • 1
    Please clarify what you mean by "The string should not start and end with a dot" -- do you mean it shouldn't have a dot at either end, or that it's ok if it has a dot at one end as long as it doesn't also have one at the other? Similarly, when you say it "does not filter" the examples, do you mean it doesn't match them (i.e. doesn't filter them in), or that it doesn't not match them (i.e. doesn't filter them out)? Sep 27, 2021 at 1:03
  • 1
    I think your examples should be abc.def.xyz? Sep 27, 2021 at 2:27
  • The solution/fix to your RE is simple enough -- remember that * means zero or more. Change your RE to ^[^\.][^\.]*\.[^\.]*\.[^\.][^\.]*$ Sep 27, 2021 at 18:37

4 Answers 4

11

You're not saying how the string is input from the user, but note that if it may contain newline characters, you can't use grep to filter them (unless you use the --null extension) as grep works on one line at a time. Also note that the [^\.] regex matches on characters other than backslash and . and the . regex operator (or [...]) in many regex implementations will not match on bytes that don't form valid characters in the locale.

Here, to check that $string contains 2 and only 2 dots, but not at the start nor end and not next to each other, you can use the standard sh:

case $string in
  (*.*.*.* | .* | *. | *..* ) echo not OK;;
  (*.*.*) echo OK;;
  (*) echo not OK;;
esac

Or with ksh globs, a subset of which can be made available in the bash shell by doing shopt -s extglob:

case $string in
  ( +([!.]).+([!.]).+([!.]) ) echo OK;;
  (*) echo not OK;;
esac

bash can also do extended regex matching with the =~ operator inside its [[...]] ksh-style construct, but again, you'll want to fix the locale to C:

regex_match_in_C_locale() {
  local LC_ALL=C
  [[ $1 =~ $2 ]]
}

if regex_match_in_C_locale "$string" '^[^.]+\.[^.]+\.[^.]+$'; then
  echo OK
else
  echo not OK
fi

POSIXly, you can do basic regex matching with the expr utility:

if
  LC_ALL=C expr "x$string" : 'x[^.]\{1,\}\.[^.]\{1,\}\.[^.]\{1,\}$' > /dev/null
then
  echo OK
else
  echo not OK
fi

Or extended regex matching with the awk utility:

regex_match_in_C_locale() {
  LC_ALL=C awk -- 'BEGIN {exit(ARGV[1] !~ ARGV[2])}' "$@"
}
if regex_match_in_C_locale "$string" '^[^.]+\.[^.]+\.[^.]+$'; then
  echo OK
else
  echo not OK
fi
1
  • I was looking to select a string like abc.def.xyz.This pattern helped me out ^[^.]+\.[^.]+\.[^.]+$ and thanks for the detailed explanation.
    – depar
    Sep 27, 2021 at 10:24
5

I think you are looking for this regex ^[^.]\+\.[^.]\+\.[^.]\+$, in this example we will use grep:

The characters inside brackets are treated literally (except -), so you don't need to escape the dot.

$ echo ".a.b.c." | grep  "^[^.]\+\.[^.]\+\.[^.]\+$"
$ echo ".a.b.c"  | grep  "^[^.]\+\.[^.]\+\.[^.]\+$"
$ echo "a.b.c."  | grep  "^[^.]\+\.[^.]\+\.[^.]\+$"
$ echo "a..c"    | grep  "^[^.]\+\.[^.]\+\.[^.]\+$"
$ echo "a.b.c"   | grep  "^[^.]\+\.[^.]\+\.[^.]\+$"
a.b.c

The regex says

  • The string must start with one or more characters that are not dots, followed by a dot ^[^.]\+\., followed by one or more characters that are not dots and a dot [^.]\+\., followed by one or more characters that are not dots [^.]\+$ until the end of the line.
3
  • I modified your pattern a little bit ^[^.]+\.[^.]+\.[^.]+$ which worked for me, I don't why it worked, but thanks for guiding me with ^[^.] and + instead of ^[^\.] and *
    – depar
    Sep 27, 2021 at 10:19
  • 1
    @depar It depends with what tool you are using the pattern. In this case with grep the + has to be escaped for it to work. Sep 27, 2021 at 10:31
  • 3
    \+ is a GNU extension. The standard equivalent is \{1,\} or use grep -E for extended regexps, and then +. Sep 27, 2021 at 10:34
1

In awk, we can do this:

$ awk '  $0"."  ~   /^([^.]+\.){3}$/  ' file

a.b.c
abc.def.xyz

Adding a dot at the end, makes the pattern repetitive, just three times of not-dot followed by a dot. Like a. -- b. -- c. or abc. -- def. -- xyz.

Or, in regex parlance: ([^.]\.){3}

Only accept if the regex could match the whole line.

-1

If you want to do this in bash, this is one approach:

IFS="." read -ra words <<<"$input"
if ((${#words[@]} == 3)) && [[ $input != .* && $input != *. ]]; then
    echo "valid input"
fi

This one actually use the values in the words array:

IFS="." read -ra words <<<"$input"
# 3 dot-separated fields, and the first and last cannot be empty
if ((${#words[@]} == 3)) && [[ -n ${words[0]} && -n ${words[2]} ]]; then
    echo "valid input"
fi
3
  • With this a..c gives valid input, as well as a.b.c., wich I understand is not what OP wants. Sep 27, 2021 at 3:01
  • Oh, I think the examples given are wrong, they should be a.b.c, not a.b. Sep 27, 2021 at 3:04
  • IFS=. read -ra words splits a.b.c. into a, b and c only in bash, so the second approach won't work. Sep 27, 2021 at 5:19

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.