diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index e97e12060..30c2dd34d 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -881,14 +881,19 @@ cmd_pipeline_table_update(char **tokens,
 	if (file_add)
 		for (line_id = 1; ; line_id++) {
 			struct rte_swx_table_entry *entry;
+			int is_blank_or_comment;
 
 			if (fgets(line, 2048, file_add) == NULL)
 				break;
 
 			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
 				table_name,
-				line);
+				line,
+				&is_blank_or_comment);
 			if (!entry) {
+				if (is_blank_or_comment)
+					continue;
+
 				snprintf(out, out_size, MSG_FILE_ERR,
 					file_name_add, line_id);
 				goto error;
@@ -911,14 +916,19 @@ cmd_pipeline_table_update(char **tokens,
 	if (file_delete)
 		for (line_id = 1; ; line_id++) {
 			struct rte_swx_table_entry *entry;
+			int is_blank_or_comment;
 
 			if (fgets(line, 2048, file_delete) == NULL)
 				break;
 
 			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
 				table_name,
-				line);
+				line,
+				&is_blank_or_comment);
 			if (!entry) {
+				if (is_blank_or_comment)
+					continue;
+
 				snprintf(out, out_size, MSG_FILE_ERR,
 					file_name_delete, line_id);
 				goto error;
@@ -940,14 +950,19 @@ cmd_pipeline_table_update(char **tokens,
 	if (file_default)
 		for (line_id = 1; ; line_id++) {
 			struct rte_swx_table_entry *entry;
+			int is_blank_or_comment;
 
 			if (fgets(line, 2048, file_default) == NULL)
 				break;
 
 			entry = rte_swx_ctl_pipeline_table_entry_read(p->ctl,
 				table_name,
-				line);
+				line,
+				&is_blank_or_comment);
 			if (!entry) {
+				if (is_blank_or_comment)
+					continue;
+
 				snprintf(out, out_size, MSG_FILE_ERR,
 					file_name_default, line_id);
 				goto error;
diff --git a/lib/librte_pipeline/rte_swx_ctl.c b/lib/librte_pipeline/rte_swx_ctl.c
index 62ce3086d..ce3344fdc 100644
--- a/lib/librte_pipeline/rte_swx_ctl.c
+++ b/lib/librte_pipeline/rte_swx_ctl.c
@@ -1339,19 +1339,32 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl)
 		table_abort(ctl, i);
 }
 
+static int
+token_is_comment(const char *token)
+{
+	if ((token[0] == '#') ||
+	    (token[0] == ';') ||
+	    ((token[0] == '/') && (token[1] == '/')))
+		return 1; /* TRUE. */
+
+	return 0; /* FALSE. */
+}
+
 #define RTE_SWX_CTL_ENTRY_TOKENS_MAX 256
 
 struct rte_swx_table_entry *
 rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,
 				      const char *table_name,
-				      const char *string)
+				      const char *string,
+				      int *is_blank_or_comment)
 {
-	char *tokens[RTE_SWX_CTL_ENTRY_TOKENS_MAX];
+	char *token_array[RTE_SWX_CTL_ENTRY_TOKENS_MAX], **tokens;
 	struct table *table;
 	struct action *action;
 	struct rte_swx_table_entry *entry = NULL;
 	char *s0 = NULL, *s;
 	uint32_t n_tokens = 0, arg_offset = 0, i;
+	int blank_or_comment = 0;
 
 	/* Check input arguments. */
 	if (!ctl)
@@ -1381,37 +1394,66 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,
 		char *token;
 
 		token = strtok_r(s, " \f\n\r\t\v", &s);
-		if (!token)
+		if (!token || token_is_comment(token))
 			break;
 
 		if (n_tokens >= RTE_SWX_CTL_ENTRY_TOKENS_MAX)
 			goto error;
 
-		tokens[n_tokens] = token;
+		token_array[n_tokens] = token;
 		n_tokens++;
 	}
 
-	if ((n_tokens < 3 + table->info.n_match_fields) ||
-	    strcmp(tokens[0], "match") ||
-	    strcmp(tokens[1 + table->info.n_match_fields], "action"))
-		goto error;
-
-	action = action_find(ctl, tokens[2 + table->info.n_match_fields]);
-	if (!action)
+	if (!n_tokens) {
+		blank_or_comment = 1;
 		goto error;
+	}
 
-	if (n_tokens != 3 + table->info.n_match_fields +
-	    action->info.n_args * 2)
-		goto error;
+	tokens = token_array;
 
 	/*
 	 * Match.
 	 */
+	if (n_tokens && strcmp(tokens[0], "match"))
+		goto action;
+
+	if (n_tokens < 1 + table->info.n_match_fields)
+		goto error;
+
 	for (i = 0; i < table->info.n_match_fields; i++) {
 		struct rte_swx_ctl_table_match_field_info *mf = &table->mf[i];
-		char *mf_val = tokens[1 + i];
-		uint64_t val;
+		char *mf_val = tokens[1 + i], *mf_mask = NULL;
+		uint64_t val, mask = UINT64_MAX;
+		uint32_t offset = (mf->offset - table->mf[0].offset) / 8;
+
+		/*
+		 * Mask.
+		 */
+		mf_mask = strchr(mf_val, '/');
+		if (mf_mask) {
+			*mf_mask = 0;
+			mf_mask++;
+
+			/* Parse. */
+			mask = strtoull(mf_mask, &mf_mask, 0);
+			if (mf_mask[0])
+				goto error;
+
+			/* Endianness conversion. */
+			if (mf->is_header)
+				mask = field_hton(mask, mf->n_bits);
+		}
 
+			/* Copy to entry. */
+			if (entry->key_mask)
+				memcpy(&entry->key_mask[offset],
+				       (uint8_t *)&mask,
+				       mf->n_bits / 8);
+
+		/*
+		 * Value.
+		 */
+		/* Parse. */
 		val = strtoull(mf_val, &mf_val, 0);
 		if (mf_val[0])
 			goto error;
@@ -1420,17 +1462,32 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,
 		if (mf->is_header)
 			val = field_hton(val, mf->n_bits);
 
-		/* Copy key and key_mask to entry. */
-		memcpy(&entry->key[(mf->offset - table->mf[0].offset) / 8],
+		/* Copy to entry. */
+		memcpy(&entry->key[offset],
 		       (uint8_t *)&val,
 		       mf->n_bits / 8);
-
-		/* TBD Set entry->key_mask for wildcard and LPM tables. */
 	}
 
+	tokens += 1 + table->info.n_match_fields;
+	n_tokens -= 1 + table->info.n_match_fields;
+
 	/*
 	 * Action.
 	 */
+action:
+	if (n_tokens && strcmp(tokens[0], "action"))
+		goto other;
+
+	if (n_tokens < 2)
+		goto error;
+
+	action = action_find(ctl, tokens[1]);
+	if (!action)
+		goto error;
+
+	if (n_tokens < 2 + action->info.n_args * 2)
+		goto error;
+
 	/* action_id. */
 	entry->action_id = action - ctl->actions;
 
@@ -1441,8 +1498,8 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,
 		uint64_t val;
 		int is_nbo = 0;
 
-		arg_name = tokens[3 + table->info.n_match_fields + i * 2];
-		arg_val = tokens[3 + table->info.n_match_fields + i * 2 + 1];
+		arg_name = tokens[2 + i * 2];
+		arg_val = tokens[2 + i * 2 + 1];
 
 		if (strcmp(arg_name, arg->name) ||
 		    (strlen(arg_val) < 4) ||
@@ -1473,15 +1530,50 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,
 		arg_offset += arg->n_bits / 8;
 	}
 
+	tokens += 2 + action->info.n_args * 2;
+	n_tokens -= 2 + action->info.n_args * 2;
+
+other:
+	if (n_tokens)
+		goto error;
+
 	free(s0);
 	return entry;
 
 error:
 	table_entry_free(entry);
 	free(s0);
+	if (is_blank_or_comment)
+		*is_blank_or_comment = blank_or_comment;
 	return NULL;
 }
 
+static void
+table_entry_printf(FILE *f,
+		   struct rte_swx_ctl_pipeline *ctl,
+		   struct table *table,
+		   struct rte_swx_table_entry *entry)
+{
+	struct action *action = &ctl->actions[entry->action_id];
+	uint32_t i;
+
+	fprintf(f, "match ");
+	for (i = 0; i < table->params.key_size; i++)
+		fprintf(f, "%02x", entry->key[i]);
+
+	if (entry->key_mask) {
+		fprintf(f, "/");
+		for (i = 0; i < table->params.key_size; i++)
+			fprintf(f, "%02x", entry->key_mask[i]);
+	}
+
+	fprintf(f, " action %s ", action->info.name);
+	for (i = 0; i < action->data_size; i++)
+		fprintf(f, "%02x", entry->action_data[i]);
+
+	fprintf(f, "\n");
+}
+
 int
 rte_swx_ctl_pipeline_table_fprintf(FILE *f,
 				   struct rte_swx_ctl_pipeline *ctl,
@@ -1512,47 +1604,17 @@ rte_swx_ctl_pipeline_table_fprintf(FILE *f,
 
 	/* Table entries. */
 	TAILQ_FOREACH(entry, &table->entries, node) {
-		struct action *action = &ctl->actions[entry->action_id];
-
-		fprintf(f, "match ");
-		for (i = 0; i < table->params.key_size; i++)
-			fprintf(f, "%02x", entry->key[i]);
-
-		fprintf(f, " action %s ", action->info.name);
-		for (i = 0; i < action->data_size; i++)
-			fprintf(f, "%02x", entry->action_data[i]);
-
-		fprintf(f, "\n");
+		table_entry_printf(f, ctl, table, entry);
 		n_entries++;
 	}
 
 	TAILQ_FOREACH(entry, &table->pending_modify0, node) {
-		struct action *action = &ctl->actions[entry->action_id];
-
-		fprintf(f, "match ");
-		for (i = 0; i < table->params.key_size; i++)
-			fprintf(f, "%02x", entry->key[i]);
-
-		fprintf(f, " action %s ", action->info.name);
-		for (i = 0; i < action->data_size; i++)
-			fprintf(f, "%02x", entry->action_data[i]);
-
-		fprintf(f, "\n");
+		table_entry_printf(f, ctl, table, entry);
 		n_entries++;
 	}
 
 	TAILQ_FOREACH(entry, &table->pending_delete, node) {
-		struct action *action = &ctl->actions[entry->action_id];
-
-		fprintf(f, "match ");
-		for (i = 0; i < table->params.key_size; i++)
-			fprintf(f, "%02x", entry->key[i]);
-
-		fprintf(f, " action %s ", action->info.name);
-		for (i = 0; i < action->data_size; i++)
-			fprintf(f, "%02x", entry->action_data[i]);
-
-		fprintf(f, "\n");
+		table_entry_printf(f, ctl, table, entry);
 		n_entries++;
 	}
 
diff --git a/lib/librte_pipeline/rte_swx_ctl.h b/lib/librte_pipeline/rte_swx_ctl.h
index 32815b69e..530671db1 100644
--- a/lib/librte_pipeline/rte_swx_ctl.h
+++ b/lib/librte_pipeline/rte_swx_ctl.h
@@ -521,6 +521,10 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl);
  *   Table name.
  * @param[in] string
  *   String containing the table entry.
+ * @param[out] is_blank_or_comment
+ *   On error, this argument provides an indication of whether *string* contains
+ *   an invalid table entry (set to zero) or a blank or comment line that should
+ *   typically be ignored (set to a non-zero value).
  * @return
  *   0 on success or the following error codes otherwise:
  *   -EINVAL: Invalid argument.
@@ -529,7 +533,8 @@ __rte_experimental
 struct rte_swx_table_entry *
 rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl,
 				      const char *table_name,
-				      const char *string);
+				      const char *string,
+				      int *is_blank_or_comment);
 
 /**
  * Pipeline table print to file
