Compile with
gcc -o trivial_grep trivial_grep.c -lpcre2-8
source:
#define PCRE2_CODE_UNIT_WIDTH 8

#include <stdio.h>
#include <string.h>
#include <pcre2.h>

int main(int argc, char* argv[])
{
  // for iterating through argv
  int i;

  // for pcre2
  pcre2_code *re;
  PCRE2_SPTR pattern;
  PCRE2_SPTR subject;
  PCRE2_SPTR name_table;

  int crlf_is_newline;
  int errornumber;
  int find_all;
  int rc;
  int utf8;

  uint32_t option_bits;
  uint32_t namecount;
  uint32_t name_entry_size;
  uint32_t newline;

  PCRE2_SIZE erroroffset;
  PCRE2_SIZE *ovector;
  PCRE2_SIZE subject_length;

  pcre2_match_data *match_data;

  // for reading from a file
  char *filename;
  FILE *fp;
  char *line = NULL;
  size_t len = 0;
  ssize_t read;

  if( argc != 3 ) {
    printf("Exactly two arguments are required, a regex and a filename");
  }

  // open file
  filename = argv[2];
  fp = fopen(filename,"r");
  if( fp == NULL ) {
    printf("Failed to open %s\n",filename);
    return 1;
  }
 
  // compile pattern
  pattern = (PCRE2_SPTR)argv[1];
  re = pcre2_compile(
    pattern,
    PCRE2_ZERO_TERMINATED,
    0,
    &errornumber,
    &erroroffset,
    NULL);

  // see if pattern compile succeeded
  if( re == NULL ) {
    PCRE2_UCHAR buffer[256];
    pcre2_get_error_message(errornumber,buffer,sizeof(buffer));
    printf("PCRE2 compilation failed at offset %d: %s\n", (int)erroroffset,buffer);
    return 1;
  }

  // create struct for pattern match data
  match_data = pcre2_match_data_create_from_pattern(re, NULL);

  // iterate over lines in file
  while((read = getline(&line,&len,fp)) != -1) {
    subject = (PCRE2_SPTR)line;
    subject_length = (PCRE2_SIZE)read;

    rc = pcre2_match(
      re,
      subject,
      subject_length,
      0,
      0,
      match_data,
      NULL);

    if( rc > 0 ) {
      printf("%s",line);
    }
  }

  // cleanup
  pcre2_match_data_free(match_data);
  pcre2_code_free(re);

  // done
  return 0;
}
The demo at https://www.pcre.org/current/doc/html/pcre2demo.html was the basis for this.