Quick summary
Summarize this blog with AI
Introduction
grepl() returns one logical match result per input string; grep() returns matching positions or values. Start with the smallest pattern that expresses your actual contract. Literal text, an entire-field format and a substring search are different questions.
Tested with R 4.5.1. Run the following code blocks in order. All inputs are included.
Distinguish literal text from regex metacharacters
Use fixed = TRUE when the search string is literal. A regex dot matches a character rather than a literal period.
x <- c("file.csv", "fileXcsv", "other.csv", NA_character_)
literal <- grepl("file.csv", x, fixed = TRUE)
print(literal) # TRUE FALSE FALSE FALSE
stopifnot(identical(literal, c(TRUE, FALSE, FALSE, FALSE)))
regex <- grepl("file.csv", x)
stopifnot(identical(regex, c(TRUE, TRUE, FALSE, FALSE)))
stopifnot(identical(grep("file.csv", x, fixed = TRUE), 1L))
For a nonmissing pattern, grepl treats a missing input string as a non-match. If missing text means “unknown,” preserve that status explicitly rather than interpreting FALSE as evidence the text was inspected.
Match a whole field with anchors
This example screens a deliberately narrow phone layout: three ASCII digits, a dash, three digits, a dash, four digits. It does not establish whether a number exists or is dialable.
phones <- c("123-456-7890", "9876543210", "456-789-1234", "x123-456-7890", NA_character_)
pattern <- "^[0-9]{3}-[0-9]{3}-[0-9]{4}$"
format_ok <- grepl(pattern, phones)
format_ok[is.na(phones)] <- NA
print(format_ok) # TRUE FALSE TRUE FALSE NA
stopifnot(identical(format_ok, c(TRUE, FALSE, TRUE, FALSE, NA)))
# Equivalent digit shorthand with explicit engine and source escaping:
stopifnot(identical(grepl("\\A\\d{3}-\\d{3}-\\d{4}\\z", phones, perl = TRUE),
grepl(pattern, phones)))
stopifnot(!grepl(pattern, "123-456-7890\n"))
stopifnot(!grepl("\\A\\d{3}-\\d{3}-\\d{4}\\z", "123-456-7890\n", perl = TRUE))
Use the correct R string escaping when a regex needs a backslash. Anchors restrict the match to the whole field; omitting them would accept a valid-looking substring inside unrelated text.
Keep address screening narrower than validation
If your task is membership in a known domain, an anchored suffix is more direct than a broad email regex. A successful suffix match still does not validate the mailbox.
addresses <- c("[email protected]", "ada@exampleXcom", "[email protected]", "not-an-address", NA_character_)
at_domain <- grepl("@example[.]com$", addresses, ignore.case = TRUE)
at_domain[is.na(addresses)] <- NA
stopifnot(identical(at_domain, c(TRUE, FALSE, FALSE, FALSE, NA)))
keywords <- c("love", "friendly", "amazing")
reviews <- c("Friendly service", "not amazing", "ordinary", NA_character_)
# Normalize case first for literal keyword matching.
normalized <- tolower(reviews)
has_keyword <- vapply(normalized, function(text) {
if (is.na(text)) return(NA)
any(vapply(keywords, function(word) grepl(word, text, fixed = TRUE), logical(1)))
}, logical(1))
stopifnot(identical(unname(has_keyword), c(TRUE, TRUE, FALSE, NA)))
“Not amazing” contains a keyword but is not necessarily positive. Label the output as keyword presence, not sentiment. Email delivery or ownership requires checks beyond text shape, and Unicode/address standards need a purpose-built parser when that is the requirement.
Test empty, missing and special-character inputs
Keep a missing-value policy separate from matching and use fixed matching for literal patterns that contain regex symbols.
stopifnot(length(grepl("x", character(0))) == 0)
stopifnot(!grepl("x", ""), grepl("", ""))
stopifnot(grepl("a+b", "a+b", fixed = TRUE))
stopifnot(!grepl("a+b", "a+b"))
stopifnot(grepl("a+b", "aaab"))
An empty pattern can match an empty string, so validate a search box separately if blank searches are not meaningful. Avoid building an unbounded complex regex from untrusted text. See gsub for replacement and strsplit for tokens.