diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 47f935d20..3d7f90360 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -12579,6 +12579,27 @@ cmdline_parse_ctx_t main_ctx[] = {
 	NULL,
 };
 
+/* read cmdline commands from file */
+void
+cmdline_read_from_file(const char *filename)
+{
+	struct cmdline *cl;
+
+	cl = cmdline_file_new(main_ctx, "testpmd> ", filename);
+	if (cl == NULL) {
+		printf("Failed to create file based cmdline context: %s\n",
+		       filename);
+		return;
+	}
+
+	cmdline_interact(cl);
+	cmdline_quit(cl);
+
+	cmdline_free(cl);
+
+	printf("Read CLI commands from %s\n", filename);
+}
+
 /* prompt function, called from main on MASTER lcore */
 void
 prompt(void)
diff --git a/app/test-pmd/parameters.c b/app/test-pmd/parameters.c
index 28db8cdd3..085b6fc73 100644
--- a/app/test-pmd/parameters.c
+++ b/app/test-pmd/parameters.c
@@ -86,6 +86,7 @@ usage(char* progname)
 	printf("usage: %s "
 #ifdef RTE_LIBRTE_CMDLINE
 	       "[--interactive|-i] "
+	       "[--cmdline-file=FILENAME] "
 #endif
 	       "[--help|-h] | [--auto-start|-a] | ["
 	       "--coremask=COREMASK --portmask=PORTMASK --numa "
@@ -103,6 +104,7 @@ usage(char* progname)
 	       progname);
 #ifdef RTE_LIBRTE_CMDLINE
 	printf("  --interactive: run in interactive mode.\n");
+	printf("  --cmdline-file: execute cli commands before startup.\n");
 #endif
 	printf("  --auto-start: start forwarding on init "
 	       "[always when non-interactive].\n");
@@ -505,6 +507,7 @@ launch_args_parse(int argc, char** argv)
 		{ "help",			0, 0, 0 },
 #ifdef RTE_LIBRTE_CMDLINE
 		{ "interactive",		0, 0, 0 },
+		{ "cmdline-file",		1, 0, 0 },
 		{ "auto-start",			0, 0, 0 },
 		{ "eth-peers-configfile",	1, 0, 0 },
 		{ "eth-peer",			1, 0, 0 },
@@ -595,6 +598,13 @@ launch_args_parse(int argc, char** argv)
 				printf("Interactive-mode selected\n");
 				interactive = 1;
 			}
+			if (!strcmp(lgopts[opt_idx].name, "cmdline-file")) {
+				printf("CLI commands to be read from %s\n",
+				       optarg);
+				snprintf(cmdline_filename,
+					 sizeof(cmdline_filename), "%s",
+					 optarg);
+			}
 			if (!strcmp(lgopts[opt_idx].name, "auto-start")) {
 				printf("Auto-start selected\n");
 				auto_start = 1;
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index e04e215ba..f7722d91f 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -87,6 +87,7 @@ uint16_t verbose_level = 0; /**< Silent by default. */
 /* use master core for command line ? */
 uint8_t interactive = 0;
 uint8_t auto_start = 0;
+char cmdline_filename[PATH_MAX] = {0};
 
 /*
  * NUMA support configuration.
@@ -2140,6 +2141,9 @@ main(int argc, char** argv)
 		rte_eth_promiscuous_enable(port_id);
 
 #ifdef RTE_LIBRTE_CMDLINE
+	if (strlen(cmdline_filename) != 0)
+		cmdline_read_from_file(cmdline_filename);
+
 	if (interactive == 1) {
 		if (auto_start) {
 			printf("Start automatic packet forwarding\n");
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 8cf286052..554896dc6 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -308,6 +308,7 @@ extern uint16_t nb_rx_queue_stats_mappings;
 extern uint16_t verbose_level; /**< Drives messages being displayed, if any. */
 extern uint8_t  interactive;
 extern uint8_t  auto_start;
+extern char cmdline_filename[PATH_MAX]; /**< offline commands file */
 extern uint8_t  numa_support; /**< set by "--numa" parameter */
 extern uint16_t port_topology; /**< set by "--port-topology" parameter */
 extern uint8_t no_flush_rx; /**<set by "--no-flush-rx" parameter */
@@ -492,6 +493,7 @@ unsigned int parse_item_list(char* str, const char* item_name,
 			unsigned int max_items,
 			unsigned int *parsed_items, int check_unique_values);
 void launch_args_parse(int argc, char** argv);
+void cmdline_read_from_file(const char *filename);
 void prompt(void);
 void prompt_exit(void);
 void nic_stats_display(portid_t port_id);
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index bdc6a14df..4bf0f799a 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -86,6 +86,45 @@ These are divided into sections and can be accessed using help, help section or
        help all        : All of the above sections.
 
 
+Command File Functions
+----------------------
+
+To facilitate loading large number of commands or to avoid cutting and pasting where not
+practical or possible testpmd supports alternative methods for executing commands.
+
+* If started with the ``--cmdline-file=FILENAME`` command line argument testpmd
+  will execute all CLI commands contained within the file immediately before
+  starting packet forwarding or entering interactive mode.
+
+.. code-block:: console
+
+   ./testpmd -n4 -r2 ... -- -i --cmdline-file=/home/ubuntu/flow-create-commands.txt
+   Interactive-mode selected
+   CLI commands to be read from /home/ubuntu/flow-create-commands.txt
+   Configuring Port 0 (socket 0)
+   Port 0: 7C:FE:90:CB:74:CE
+   Configuring Port 1 (socket 0)
+   Port 1: 7C:FE:90:CB:74:CA
+   Checking link statuses...
+   Port 0 Link Up - speed 10000 Mbps - full-duplex
+   Port 1 Link Up - speed 10000 Mbps - full-duplex
+   Done
+   Flow rule #0 created
+   Flow rule #1 created
+   ...
+   ...
+   Flow rule #498 created
+   Flow rule #499 created
+   Read all CLI commands from /home/ubuntu/flow-create-commands.txt
+   testpmd>
+
+
+In all cases output from any included command will be displayed as standard output.
+Execution will continue until the end of the file is reached regardless of
+whether any errors occur.  The end user must examine the output to determine if
+any failures occurred.
+
+
 Control Functions
 -----------------
 
