Sugerencias y solución de problemas cuando escribes analizadores

Compatible con:

En este documento, se describen los problemas que puedes encontrar cuando escribes código del analizador.

Cuando escribes código de analizador, es posible que encuentres errores cuando las instrucciones de análisis no funcionen como se espera. Las situaciones que pueden generar errores incluyen las siguientes:

  • Un patrón Grok falla.
  • Una operación rename o replace falla.
  • Errores de sintaxis en el código del analizador

Prácticas comunes en el código del analizador

En las siguientes secciones, se describen las prácticas recomendadas, los consejos y las soluciones. para ayudar a solucionar problemas.

Evita usar puntos o guiones en los nombres de las variables

El uso de guiones y puntos en los nombres de las variables puede provocar un comportamiento inesperado a menudo cuando se realizan operaciones de merge para almacenar valores en campos UDM. También puedes encontrar problemas intermitentes de análisis.

Por ejemplo, no uses los siguientes nombres de variables:

  • my.variable.result
  • my-variable-result

En su lugar, usa el siguiente nombre de variable: my_variable_result.

No uses términos con significado especial como nombre de variable

Algunas palabras, como event y timestamp, pueden tener un significado especial en el analizador código.

La cadena event suele usarse para representar un solo registro UDM y se utiliza en la sentencia @output. Si un mensaje de registro incluye un campo llamado event, o si defines una variable intermedia llamada event, y el analizador usa la palabra event en la sentencia @output, obtendrás un mensaje de error acerca de un conflicto de nombres.

Cambia el nombre de la variable intermedia por otra opción o usa el término event1 como un prefijo en los nombres de campo de UDM y en la sentencia @output.

La palabra timestamp representa la marca de tiempo creada del registro sin procesar original. R el valor establecido en esta variable intermedia se guarda Campo de UDM metadata.event_timestamp. El término @timestamp representa la fecha y la hora en que se analizó el registro sin procesar para crear un registro UDM.

En el siguiente ejemplo, se establece el campo de UDM metadata.event_timestamp en la fecha y la hora en que se analizó el registro sin procesar.

 # Save the log parse date and time to the timestamp variable
  mutate {
     rename => {
       "@timestamp" => "timestamp"
     }
   }

En el siguiente ejemplo, se establece el campo UDM metadata.event_timestamp en la fecha y tiempo extraído del registro sin procesar original y almacenado en el when intermedio de salida.

   # Save the event timestamp to timestamp variable
   mutate {
     rename => {
       "when" => "timestamp"
     }
   }

No uses los siguientes términos como variables:

  • collectiontimestamp
  • createtimestamp
  • evento
  • filename
  • mensaje
  • espacio de nombres
  • output
  • recuentodeerrores
  • timestamp
  • Zona horaria

Almacenar cada valor de datos en un campo de UDM independiente

No almacenes varios campos en un solo campo UDM mediante la concatenación con un delimitador. A continuación, se muestra un ejemplo:

"principal.user.first_name" => "first:%{first_name},last:%{last_name}"

En su lugar, almacena cada valor en un campo de UDM independiente.

"principal.user.first_name" => "%{first_name}"
"principal.user.last_name" => "%{last_name}"

Usa espacios en lugar de tabulaciones en el código

No uses tabulaciones en el código del analizador. Usa solo espacios y aplica sangría de 2 espacios a la vez.

No realizar varias acciones de combinación en una sola operación

Si fusionas varios campos en una sola operación, es posible que resultados incoherentes. En su lugar, coloca las sentencias merge en diferentes las operaciones.

Por ejemplo, reemplaza el siguiente ejemplo:

mutate {
  merge => {
      "security_result.category_details" => "category_details"
      "security_result.category_details" => "super_category_details"
  }
}

Con este bloque:

mutate {
  merge => {
    "security_result.category_details" => "category_details"
  }
}

mutate {
  merge => {
    "security_result.category_details" => "super_category_details"
  }
}

Cómo elegir expresiones condicionales if frente a if else

Si el valor condicional que está probando solo puede tener una sola coincidencia Luego, usa la sentencia condicional if else. Este enfoque es un poco más eficiente. Sin embargo, si tienes una situación en la que el valor probado podría tener una coincidencia mayor de una vez, usa varias sentencias if distintas y ordénalas desde el caso más genérico hasta el más específico.

Elige un conjunto representativo de archivos de registro para probar los cambios del analizador

Una práctica recomendada es probar el código del analizador usando muestras de registros sin procesar con un diversos formatos. Esto te permite encontrar registros únicos o casos extremos que debería manejar el analizador.

Agrega comentarios descriptivos al código del analizador

Agregar comentarios al código del analizador que expliquen por qué la instrucción es importante, en lugar que lo que hace la instrucción. El comentario ayuda a cualquier persona que mantenga el analizador para seguir el flujo. A continuación, se muestra un ejemplo:

# only assign a Namespace if the source address is RFC 1918 or Loopback IP address
if [jsonPayload][id][orig_h] =~ /^(127(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{3\}$)|(10(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{3\}$)|(192\.168(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{2\}$)|(172\.(?:1[6-9]|2\d|3[0-1])(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{2\}$)/ {
  mutate {
    replace => {
      "event1.idm.read_only_udm.principal.namespace" => "%{resource.labels.project_id}"
    }
  }
}

Inicializa variables intermedias con anticipación

Antes de extraer valores del registro original sin procesar, inicializa los valores intermedios variables que se usarán para almacenar valores de prueba.

Esto evita que se muestre un error que indique que la variable intermedia no existe.

La siguiente instrucción asigna el valor de la variable product a el campo UDM metadata.product_name.

mutate{
  replace => {
    "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
  }
}

Si la variable product no existe, verás el siguiente error:

"generic::invalid_argument: pipeline failed: filter mutate (4) failed: replace failure: field \"event1.idm.read_only_udm.metadata.product_name\": source field \"product\": field not set"

Puedes agregar una sentencia on_error para detectar el error. A continuación, se muestra un ejemplo:

mutate{
  replace => {
    "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
    }
  on_error => "_error_does_not_exist"
  }

La sentencia del ejemplo anterior captura correctamente el error de análisis en una variable booleana intermedia, llamada _error_does_not_exist. No te permiten usar la variable product en una sentencia condicional, por ejemplo, if. A continuación, se muestra un ejemplo:

if [product] != "" {
  mutate{
    replace => {
      "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
    }
  }
  on_error => "_error_does_not_exist"
}

En el ejemplo anterior, se muestra el siguiente error porque la cláusula condicional if no admite sentencias on_error:

"generic::invalid_argument: pipeline failed: filter conditional (4) failed: failed to evaluate expression: generic::invalid_argument: "product" not found in state data"

Para resolver esto, agrega un bloque de sentencia separado que inicialice el intermedio variables antes de ejecutar las instrucciones de filtro de extracción (json, csv, xml, kv o grok). A continuación, se muestra un ejemplo.

filter {
  # Initialize intermediate variables for any field you will use for a conditional check
  mutate {
    replace => {
      "timestamp" => ""
      "does_not_exist" => ""
    }
  }

  # load the logs fields from the message field
  json {
    source         => "message"
    array_function => "split_columns"
    on_error       => "_not_json"
  }
}

El fragmento actualizado del código del analizador controla las múltiples situaciones con un una sentencia condicional para verificar si el campo existe. Además, el La sentencia on_error controla los errores que pueden ocurrir.

Convertir SHA-256 a base64

En el siguiente ejemplo, se extrae el valor SHA-256, se lo codifica en base64 y se convierte a una cadena hexadecimal y reemplaza campos específicos los valores extraídos y procesados.

if [Sha256] != "" 
{
  base64
  {
  encoding => "RawStandard"
  source => "Sha256"
  target => "base64_sha256"
  on_error => "base64_message_error"
  }
  mutate
  {
    convert =>
    {
      "base64_sha256" => "bytestohex"
    }
    on_error => "already_a_string"
  }
  mutate
  {
    replace => 
  {
     "event.idm.read_only_udm.network.tls.client.certificate.sha256" => "%{base64_sha256}"
     "event.idm.read_only_udm.target.resource.name" => "%{Sha256}"
  }
  }
}

Soluciona errores en las instrucciones del analizador

No es inusual que los registros entrantes tengan un formato de registro inesperado si tienen datos mal formateados.

Puedes compilar el analizador para que maneje estos errores. Una práctica recomendada es agregar on_error al filtro de extracción y, luego, al probar la variable intermedia antes de continuar con el siguiente segmento de la lógica del analizador.

En el siguiente ejemplo, se usa el filtro de extracción json con un on_error para configurar la variable booleana _not_json. Si estableces _not_json como true significa que la entrada de registro entrante no estaba en un formato JSON válido y el entrada de registro no se analizó correctamente. Si la variable _not_json es false, que la entrada de registro entrante tenía un formato JSON válido.

 # load the incoming log from the default message field
  json {
    source         => "message"
    array_function => "split_columns"
    on_error       => "_not_json"
  }

También puedes probar si un campo tiene el formato correcto. En el siguiente ejemplo, verifica si _not_json está configurado como true, lo que indica que el registro no estaba en el formato esperado.

 # Test that the received log matches the expected format
  if [_not_json] {
    drop { tag => "TAG_MALFORMED_MESSAGE" }
  } else {
    # timestamp is always expected
    if [timestamp] != "" {

      # ...additional parser logic goes here …

    } else {

      # if the timestamp field does not exist, it's not a log source
      drop { tag => "TAG_UNSUPPORTED" }
    }
  }

Esto garantiza que el análisis no falle si los registros se transfieren con un formato incorrecto. para el tipo de registro especificado.

Usa el filtro drop con la variable tag para que la condición se capture en el Tabla de métricas de transferencia en BigQuery.

  • TAG_UNSUPPORTED
  • TAG_MALFORMED_ENCODING
  • TAG_MALFORMED_MESSAGE
  • TAG_NO_SECURITY_VALUE

El filtro drop evita que el analizador procese el registro sin procesar y normalice los campos. y crear un registro de UDM. El registro original sin procesar aún se transfiere a Google Security Operations y puede ser por búsqueda de registros sin procesar en Google Security Operations.

El valor que se pasa a la variable tag se almacena en el archivo drop_reason_code. en la Tabla de métricas de transferencia. Puedes ejecutar una consulta ad hoc en la tabla, similar a la siguiente:

SELECT
  log_type,
  drop_reason_code,
  COUNT(drop_reason_code) AS count
FROM `datalake.ingestion_metrics`
GROUP BY 1,2
ORDER BY 1 ASC

Solucionar errores de validación

Cuando compiles un analizador, puede que encuentres errores relacionados con la validación, por ejemplo, ejemplo, no se establece un campo obligatorio en el registro de UDM. El error puede ser similar al siguiente lo siguiente:

Error: generic::unknown: invalid event 0: LOG_PARSING_GENERATED_INVALID_EVENT: "generic::invalid_argument: udm validation failed: target field is not set"

El código del analizador se ejecuta correctamente, pero el registro UDM generado no se ejecuta correctamente Incluye todos los campos de UDM obligatorios según el valor establecido en metadata.event_type. El a continuación, se incluyen algunos ejemplos adicionales que podrían causar este error:

  • Si metadata.event_type es USER_LOGIN y no se configura el campo UDM target.user value.
  • Si metadata.event_type es NETWORK_CONNECTION y la target.hostnameEl campo de UDM no está configurado.

Para obtener más información sobre el campo de UDM metadata.event_type y los campos obligatorios consulta la guía de uso de UDM.

Una opción para solucionar este tipo de error es comenzar por establecer valores estáticos en UDM . Después de definir todos los campos UDM necesarios, examina el registro original sin procesar para ver cuál valores para analizar y guardar en el registro UDM. Si el registro sin procesar original no contienen ciertos campos, tal vez debas establecer valores predeterminados.

La siguiente es una plantilla de ejemplo, específica para un tipo de evento USER_LOGIN, que ilustra este enfoque.

Ten en cuenta lo siguiente:

  • La plantilla inicializa variables intermedias y configura cada una en una cadena estática.
  • El código de la sección Asignación de campos establece los valores en las variables intermedias. a campos de UDM.

Puedes expandir este código agregando variables intermedias y campos UDM adicionales. Después de que identifiques todos los campos de UDM que se deben propagar, haz lo siguiente:

  • En la sección Configuración de entrada, agrega código que extraiga campos de el registro original sin procesar y configura los valores en las variables intermedias.

  • En la sección Date Extract, agrega código que extraiga la marca de tiempo del evento. del registro original sin procesar, lo transforma y lo establece en la variable intermedia.

  • Según sea necesario, reemplaza el valor inicializado establecido en cada variable intermedia por una cadena vacía.

filter {
 mutate {
   replace => {
     # UDM > Metadata
     "metadata_event_timestamp"    => ""
     "metadata_vendor_name"        => "Example"
     "metadata_product_name"       => "Example SSO"
     "metadata_product_version"    => "1.0"
     "metadata_product_event_type" => "login"
     "metadata_product_log_id"     => "12345678"
     "metadata_description"        => "A user logged in."
     "metadata_event_type"         => "USER_LOGIN"

     # UDM > Principal
     "principal_ip"       => "192.168.2.10"

     # UDM > Target
     "target_application"            => "Example Connect"
     "target_user_user_display_name" => "Mary Smith"
     "target_user_userid"            => "mary@example.com"

     # UDM > Extensions
     "auth_type"          => "SSO"
     "auth_mechanism"     => "USERNAME_PASSWORD"

     # UDM > Security Results
     "securityResult_action"         => "ALLOW"
     "security_result.severity"       => "LOW"

   }
 }

 # ------------ Input Configuration  --------------
  # Extract values from the message using one of the extraction filters: json, kv, grok

 # ------------ Date Extract  --------------
 # If the  date {} function is not used, the default is the normalization process time

  # ------------ Field Assignment  --------------
  # UDM Metadata
  mutate {
    replace => {
      "event1.idm.read_only_udm.metadata.vendor_name"        =>  "%{metadata_vendor_name}"
      "event1.idm.read_only_udm.metadata.product_name"       =>  "%{metadata_product_name}"
      "event1.idm.read_only_udm.metadata.product_version"    =>  "%{metadata_product_version}"
      "event1.idm.read_only_udm.metadata.product_event_type" =>  "%{metadata_product_event_type}"
      "event1.idm.read_only_udm.metadata.product_log_id"     =>  "%{metadata_product_log_id}"
      "event1.idm.read_only_udm.metadata.description"        =>  "%{metadata_description}"
      "event1.idm.read_only_udm.metadata.event_type"         =>  "%{metadata_event_type}"
    }
  }

  # Set the UDM > auth fields
  mutate {
    replace => {
      "event1.idm.read_only_udm.extensions.auth.type"        => "%{auth_type}"
    }
    merge => {
      "event1.idm.read_only_udm.extensions.auth.mechanism"   => "auth_mechanism"
    }
  }

  # Set the UDM > principal fields
  mutate {
    merge => {
      "event1.idm.read_only_udm.principal.ip"                => "principal_ip"
    }
  }

  # Set the UDM > target fields
  mutate {
    replace => {
      "event1.idm.read_only_udm.target.user.userid"             =>  "%{target_user_userid}"
      "event1.idm.read_only_udm.target.user.user_display_name"  =>  "%{target_user_user_display_name}"
      "event1.idm.read_only_udm.target.application"             =>  "%{target_application}"
    }
  }

  # Set the UDM > security_results fields
  mutate {
    merge => {
      "security_result.action" => "securityResult_action"
    }
  }

  # Set the security result
  mutate {
    merge => {
      "event1.idm.read_only_udm.security_result" => "security_result"
    }
  }

 # ------------ Output the event  --------------
  mutate {
    merge => {
      "@output" => "event1"
    }
  }

}

Analiza texto no estructurado con una función Grok

Cuando se usa una función Grok para extraer valores de texto no estructurado, puedes puedes usar patrones de Grok predefinidos y expresiones regulares. Patrones de Grok facilitan la lectura del código. Si la expresión regular no incluye la abreviatura caracteres (como \w, \s), puedes copiar y pegar la sentencia directamente. en el código del analizador.

Debido a que los patrones Grok son una capa de abstracción adicional en la sentencia, es posible que y la solución de problemas es más compleja cuando se produce un error. A continuación, se muestra un ejemplo Función Grok que contiene patrones Grok predefinidos y expresiones regulares.

grok {
  match => {
    "message" => [
      "%{NUMBER:when}\\s+\\d+\\s%{SYSLOGHOST:srcip} %{WORD:action}\\/%{NUMBER:returnCode} %{NUMBER:size} %{WORD:method} (?P<url>\\S+) (?P<username>.*?) %{WORD}\\/(?P<tgtip>\\S+).*"
    ]
  }
}

Una instrucción de extracción sin patrones de Grok puede tener un mejor rendimiento. Para En el siguiente ejemplo, se requieren menos de la mitad de los pasos de procesamiento para coincidir. Esta es una consideración importante para una fuente de registro de volumen potencialmente alto.

Comprender las diferencias entre las expresiones regulares RE2 y PCRE

Los analizadores de Google Security Operations usan RE2. que el motor de expresiones regulares. Si conoces la sintaxis PCRE, puedes y noten las diferencias. A continuación, se muestra un ejemplo:

La siguiente es una declaración de PCRE: (?<_custom_field>\w+)\s

La siguiente es una sentencia RE2 para el código del analizador: (?P<_custom_field>\\w+)\\s

Asegúrate de escapar los caracteres de escape.

Google Security Operations almacena los datos de registro entrantes sin procesar en formato codificado en JSON. Esto es para asegurarse de que las cadenas de caracteres que parecen ser la abreviatura de una expresión regular se interpretan como la cadena literal. Por ejemplo, \t se interpreta como la literal, en lugar de un carácter de tabulación.

El siguiente ejemplo es un registro original sin procesar y el registro con formato codificado en JSON. Observa el carácter de escape agregado delante de cada carácter de barra inversa. que rodean el término entry.

A continuación, se muestra el registro sin procesar original:

field=\entry\

A continuación, se muestra el registro convertido al formato con codificación JSON:

field=\\entry\\

Cuando uses una expresión regular en un código de analizador, debes agregar caracteres de escape adicionales caracteres si deseas extraer solo el valor. Para hacer coincidir una barra inversa en el registro original sin procesar, usa cuatro barras inversas en la instrucción de extracción.

La siguiente es una expresión regular para el código del analizador:

^field=\\\\(?P<_value>.*)\\\\$

El siguiente es el resultado que se generó. El grupo con nombre _value almacena el término entry:

"_value": "entry"

Cuando se mueve una declaración de expresión regular estándar al código del analizador, se caracteres abreviados de expresiones regulares en la instrucción de extracción. Por ejemplo, cambia \s a \\s.

No modifiques los caracteres especiales de las expresiones regulares cuando se escapen dos veces en el una declaración de extracción de datos. Por ejemplo, \\ no se modifica como \\.

La siguiente es una expresión regular estándar:

^.*?\\\"(?P<_user>[^\\]+)\\\"\s(?:(logged\son|logged\soff))\s.*?\\\"(?P<_device>[^\\]+)\\\"\.$

La siguiente expresión regular se modifica para que funcione dentro del código del analizador.

^.*?\\\"(?P<_user>[^\\\\]+)\\\"\\s(?:(logged\\son|logged\\soff))\\s.*?\\\"(?P<_device>[^\\\\]+)\\\"\\.$

La siguiente tabla resume cuándo una expresión regular estándar debe incluir caracteres de escape adicionales antes de incluirlos en el código del analizador.

Expresión regular Expresión regular modificada para el código del analizador Descripción del cambio
\s
\\s
Los caracteres de atajo deben escaparse.
\.
\\.
Los caracteres reservados deben tener escape.
\\"
\\\"
Los caracteres reservados deben tener escape.
\]
\\]
Los caracteres reservados deben tener escape.
\|
\\|
Los caracteres reservados deben tener escape.
[^\\]+
[^\\\\]+
Los caracteres especiales dentro de un grupo de clases de caracteres deben con escape.
\\\\
\\\\
Caracteres especiales fuera de un grupo de clases de caracteres o los caracteres abreviados no requieren un escape adicional.

Las expresiones regulares deben incluir un grupo de captura con nombre

Una expresión regular, como "^.*$", es una sintaxis RE2 válida. Sin embargo, en el analizador código, falla con el siguiente error:

"ParseLogEntry failed: pipeline failed: filter grok (0) failed: failed to parse data with all match
patterns"

Debes agregar un grupo de captura válido a la expresión. Si usas Grok de captura, incluyen un grupo de captura con nombre de forma predeterminada. Cuando se utiliza el tráfico normal de expresiones de terceros, asegúrate de incluir un grupo con nombre.

El siguiente es un ejemplo de una expresión regular en un código del analizador:

"^(?P<_catchall>.*$)"

El siguiente es el resultado, que muestra el texto asignado al grupo con nombre _catchall.

"_catchall": "User \"BOB\" logged on to workstation \"DESKTOP-01\"."

Usa un grupo llamado genérico para comenzar a medida que compilas la expresión.

Cuando crees una sentencia de extracción, comienza con una expresión que atraiga más de lo que quieres. Luego, expande la expresión un campo a la vez.

En el siguiente ejemplo, se usa un grupo con nombre (_catchall) que coincide todo el mensaje. Luego, compila la expresión por pasos mediante la coincidencia partes adicionales del texto. Con cada paso, el grupo llamado _catchall contiene menos texto original. Continúa e itera un paso a la vez para coincide con el mensaje hasta que ya no necesites el grupo con nombre _catchall.

Paso Expresión regular en el código del analizador Resultado del grupo de captura con nombre _catchall
1
"^(?P<_catchall>.*$)"
User \"BOB\" logged on to workstation \"DESKTOP-01\".
2
^User\s\\\"(?P<_catchall>.*$)
BOB\" logged on to workstation \"DESKTOP-01\".
3
^User\s\\\"(?P<_user>.*?)\\\"\s(?P<_catchall>.*$)
logged on to workstation \"DESKTOP-01\".
Continúa hasta que la expresión coincida con toda la cadena de texto.

Caracteres abreviados de escape en la expresión regular

Recuerda escapar los caracteres abreviados de las expresiones regulares cuando uses el en el código del analizador. A continuación, se muestra una cadena de texto de ejemplo y la expresión regular estándar que extrae la primera palabra, This.

  This is a sample log.

La siguiente expresión regular estándar extrae la primera palabra, This. Sin embargo, cuando ejecutas esta expresión en el código del analizador, falta la letra en el resultado. s

Expresión regular estándar Resultado del grupo de captura con nombre _firstWord
"^(?P<_firstWord>[^\s]+)\s.*$" "_firstWord": "Thi",

Esto se debe a que las expresiones regulares en el código del analizador requieren un escape adicional que se agregó a los caracteres abreviados. En el ejemplo anterior, \s debe cambiarse a \\s.

Expresión regular revisada para el código del analizador Resultado del grupo de captura con nombre _firstWord
"^(?P<_firstWord>[^\\s]+)\\s.*$" "_firstWord": "This",

Esto se aplica solo a los caracteres abreviados, como \s, \r y \t. Otros caracteres, como ``, no necesitan agregar más escape.

Ejemplo completo

En esta sección, se describen las reglas anteriores como ejemplo de extremo a extremo. Este es un una cadena de texto no estructurada y la expresión regular estándar escrita para analizar la cadena. Por último, incluye la expresión regular modificada que funciona en el código del analizador.

La siguiente es la cadena de texto original.

User "BOB" logged on to workstation "DESKTOP-01".

La siguiente es una expresión regular RE2 estándar que analiza la cadena de texto.

^.*?\\\"(?P<_user>[^\\]+)\\\"\s(?:(logged\son|logged\soff))\s.*?\\\"(?P<_device>[^\\]+)\\\"\.$

Con esta expresión, se extraen los siguientes campos.

Grupo de coincidencias Posición del carácter String de texto
Coincidencia completa 0-53
User \"BOB\" logged on to workstation \"DESKTOP-01\".
Grupo `_user` 7-10
BOB
Grupo 2. 13-22
logged on
Grupo "_device" 40-50
DESKTOP-01

Esta es la expresión modificada. Se modificó la expresión regular RE2 estándar para que funcione en el código del analizador.

^.*?\\\"(?P<_user>[^\\\\]+)\\\"\\s(?:(logged\\son|logged\\soff))\\s.*?\\\"(?P<_device>[^\\\\]+)\\\"\\.$