diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index a6a768bd7c..6ba1bb40e9 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -222,6 +222,7 @@ The public API headers are grouped by topics:
   [key/value args](@ref rte_kvargs.h),
   [string](@ref rte_string_fns.h),
   [thread](@ref rte_thread.h)
+  [cmdline args management](@ref rte_args.h),
 
 - **debug**:
   [jobstats](@ref rte_jobstats.h),
diff --git a/doc/api/doxy-api.conf.in b/doc/api/doxy-api.conf.in
index e94c9e4e46..15b540553d 100644
--- a/doc/api/doxy-api.conf.in
+++ b/doc/api/doxy-api.conf.in
@@ -28,6 +28,7 @@ INPUT                   = @TOPDIR@/doc/api/doxy-api-index.md \
                           @TOPDIR@/lib/eal/include \
                           @TOPDIR@/lib/eal/include/generic \
                           @TOPDIR@/lib/acl \
+                          @TOPDIR@/lib/args \
                           @TOPDIR@/lib/bbdev \
                           @TOPDIR@/lib/bitratestats \
                           @TOPDIR@/lib/bpf \
diff --git a/lib/args/args.c b/lib/args/args.c
new file mode 100644
index 0000000000..80eb6670da
--- /dev/null
+++ b/lib/args/args.c
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Intel Corporation
+ */
+
+#include <stdint.h>
+#include <malloc.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_eal.h>
+#include <rte_errno.h>
+#include <rte_common.h>
+#include <rte_bitops.h>
+#include "rte_args.h"
+
+struct rte_args {
+	int array_sz;
+	int arg_count;
+	char **args;
+};
+
+#define DEFAULT_SIZE_HINT 8
+
+struct rte_args *
+rte_args_alloc(uint32_t size_hint)
+{
+	struct rte_args *a;
+
+	a = malloc(sizeof(*a));
+	if (a == NULL)
+		return NULL;
+
+	if (size_hint == 0)
+		size_hint = DEFAULT_SIZE_HINT;
+	size_hint = rte_align32pow2(size_hint);
+	a->args = calloc(size_hint, sizeof(a->args[0]));
+	if (a->args == NULL) {
+		free(a);
+		return NULL;
+	}
+
+	memset(a->args, 0, sizeof(a->args[0]) * size_hint);
+	a->arg_count = 0;
+	a->array_sz = size_hint;
+	return a;
+}
+
+struct rte_args *
+rte_args_create(int argc, char **argv)
+{
+	struct rte_args *a;
+
+	if (argc <= 0 || argv == NULL)
+		return NULL;
+
+	a = rte_args_alloc(argc + DEFAULT_SIZE_HINT);
+	if (a == NULL)
+		return NULL;
+
+	if (rte_args_add_argv(a, argc, argv) != 0) {
+		rte_args_free(a);
+		return NULL;
+	}
+	return a;
+}
+
+
+int
+rte_args_add(struct rte_args *a, const char *arg)
+{
+	if (a == NULL)
+		return -1;
+
+	if (a->arg_count == a->array_sz - 1) {  /* need null at end, so subtract 1 */
+		void *new_a = reallocarray(a->args, a->array_sz * 2, sizeof(a->args[0]));
+		if (new_a == NULL)
+			return -1;
+
+		a->args = new_a;
+		/* zero out only new portion of array */
+		memset(&a->args[a->array_sz], 0, a->array_sz * sizeof(a->args[0]));
+		a->array_sz *= 2;
+	}
+
+	a->args[a->arg_count] = strdup(arg);
+	if (a->args[a->arg_count] == NULL)
+		return -1;
+	a->arg_count++;
+	return 0;
+}
+
+int
+rte_args_add_list(struct rte_args *a, int n, ...)
+{
+	va_list ap;
+	va_start(ap, n);
+	while (n > 0) {
+		if (rte_args_add(a, va_arg(ap, char *)) != 0)
+			break;
+		n--;
+	}
+	va_end(ap);
+	return n;  /* return the number failing, 0 if all ok */
+}
+
+int
+rte_args_add_argv(struct rte_args *a, int argc, char **argv)
+{
+	int i;
+
+	for (i = 0; i < argc; i++)
+		if (rte_args_add(a, argv[i]) < 0)
+			break;
+	return argc - i; /* return the number failing, 0 if all ok */
+}
+
+bool
+rte_args_has_arg(struct rte_args *a, const char *arg)
+{
+	int i;
+
+	if (a == NULL || arg == NULL)
+		return false;
+
+	for (i = 0; i < a->arg_count; i++) {
+		if (strcmp(a->args[i], arg) == 0)
+			return true;
+	}
+	return false;
+}
+
+char **
+rte_args_get_argv(struct rte_args *a, int *argc)
+{
+	if (a == NULL)
+		return NULL;
+
+	if (argc != NULL)
+		*argc = a->arg_count;
+	return a->args;
+}
+
+int
+rte_args_get_argc(struct rte_args *a)
+{
+	if (a == NULL)
+		return -1;
+
+	return a->arg_count;
+}
+
+void
+rte_args_free(struct rte_args *a)
+{
+	int i;
+
+	if (a == NULL)
+		return;
+
+	for (i = 0; i < a->arg_count; i++)
+		free(a->args[i]);
+	free(a->args);
+	free(a);
+}
+
+int
+rte_args_eal_init(struct rte_args *a)
+{
+	int argc;
+	char **argv;
+
+	/* rte_args_argv does the check for NULL for us */
+	argv = rte_args_get_argv(a, &argc);
+	if (argv == NULL || argc <= 0)
+		return -1;
+
+	return rte_eal_init(argc, argv);
+}
diff --git a/lib/args/meson.build b/lib/args/meson.build
new file mode 100644
index 0000000000..997f76a8bb
--- /dev/null
+++ b/lib/args/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Intel Corporation
+
+sources = files('args.c')
+headers = files('rte_args.h')
diff --git a/lib/args/rte_args.h b/lib/args/rte_args.h
new file mode 100644
index 0000000000..3b80b9a39c
--- /dev/null
+++ b/lib/args/rte_args.h
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Intel Corporation
+ */
+#ifndef RTE_EAL_ARGS_H
+#define RTE_EAL_ARGS_H
+
+/**
+ * @file
+ *
+ * Command-line argument manipulation functions
+ *
+ * Simple functions for manipulating sets of commandline arguments, and then
+ * initializing DPDK / EAL based on those.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+struct rte_args;
+
+/**
+ * Allocate an argument structure
+ *
+ * This functions reserves memory for an argument structure. On success the returned structure
+ * is guaranteed to hold at least size_hint arguments without being resized. It is a hint only -
+ * if more elements are added than that hinted, the rte_args structure will be dynamically resized
+ * as necessary (subject to memory being available to do so).
+ *
+ * @param size_hint
+ *   The returned structure is guaranteed to hold at least this many arguments without resizing.
+ *   If zero, a default non-zero size value will be used.
+ * @return
+ *   An empty rte_args structure, NULL on failure
+ */
+__rte_experimental
+struct rte_args *
+rte_args_alloc(uint32_t size_hint);
+
+/**
+ * Create an argument structure containing the existing parameters
+ *
+ * This functions creates an rte_args structure and initializes it with "argc" values
+ * from the "argv" array.
+ *
+ * @param argc
+ *   The number of elements in argv
+ * @param argv
+ *   Array of arguments to add to the created rte_args structure
+ * @return
+ *   An rte_args structure holding argc elements from argv. NULL on failure.
+ */
+__rte_experimental
+struct rte_args *
+rte_args_create(int argc, char **argv);
+
+/**
+ * Add a new argument to the rte_args structure
+ *
+ * Add the argument "arg" to the rte_args structure, resizing the structure as necessary.
+ * The arg parameter will be copied using strdup, so the original string may be freed or
+ * edited as necessary after the function returns.
+ *
+ * @param a
+ *   The rte_arg structure to be modified
+ * @param arg
+ *   The argument to be added
+ * @return
+ *   0 on success, -1 on failure
+ */
+__rte_experimental
+int
+rte_args_add(struct rte_args *a, const char *arg);
+
+/**
+ * Add a list of arguments to the rte_args structure
+ *
+ * Add "n" arguments, given as extra function parameters to the rte_args structure, resizing
+ * the structure as necessary.
+ * The argument parameters will be copied using strdup, so the original strings may be freed or
+ * edited as necessary after the function returns.
+ *
+ * @param a
+ *   The rte_arg structure to be modified
+ * @param n
+ *   The number of arguments to be added
+ * @return
+ *   0 on success.
+ *   On failure, returns the number of elements NOT added.
+ */
+__rte_experimental
+int
+rte_args_add_list(struct rte_args *a, int n, ...);
+
+/**
+ * Add an array of arguments to the rte_args structure
+ *
+ * Add arguments from an existing argv array to the rte_args structure.
+ * The argument parameters will be copied using strdup, so the original strings/array may
+ * be freed or edited as necessary after the function returns.
+ *
+ * @param a
+ *   The rte_arg structure to be modified
+ * @param argc
+ *   The number of arguments to be added
+ * @param argv
+ *   The array containing the pointers to the elements to be added
+ * @return
+ *   0 on success.
+ *   On failure, returns the number of elements NOT added.
+ */
+__rte_experimental
+int
+rte_args_add_argv(struct rte_args *a, int argc, char **argv);
+
+/**
+ * Query if an args list contains a given argument parameter
+ *
+ * @param a
+ *   The rte_arg structure to be modified
+ * @param arg
+ *   The argument to look for in the structure
+ * @return
+ *   True if parameters are valid and argument is found
+ *   False otherwise
+ */
+__rte_experimental
+bool
+rte_args_has_arg(struct rte_args *a, const char *arg);
+
+/**
+ * Get the array of arguments from the rte_args structure
+ *
+ * Returns an array of arguments from the structure, suitable for passing to a function which
+ * takes (argc, argv) parameters. The argc value will be optionally returned in the final output
+ * parameter if a valid pointer is passed in.
+ *
+ * NOTE: The return value points to the internals of the rte_args structure, and so the
+ * structure should not be freed while the arguments are in use.
+ *
+ * @param a
+ *   The rte_arg structure to be used
+ * @param argc
+ *   Pointer to store the count (argc) of elements returned. Ignored if NULL
+ * @return
+ *   Pointer to an array of arguments. NULL on failure.
+ */
+__rte_experimental
+char **
+rte_args_get_argv(struct rte_args *a, int *argc);
+
+/**
+ * Gets the number of arguments stored in an rte_args structure
+ *
+ * @param a
+ *   The rte_arg structure to be used
+ * @return
+ *   The number of arguments stored in the structure
+ */
+__rte_experimental
+int
+rte_args_get_argc(struct rte_args *a);
+
+/**
+ * Frees an rte_args structure
+ *
+ * @param a
+ *   The rte_arg structure to be freed
+ */
+__rte_experimental
+void
+rte_args_free(struct rte_args *a);
+
+/**
+ * Initialize DPDK EAL using arguments from the rte_args structure
+ *
+ * This function calls rte_eal_init(), passing in to it as parameters the argc, and argv
+ * values got from the rte_args structure "a".
+ *
+ * @param a
+ *   The rte_args structure to be used. Must have at least one argument in it.
+ * @return
+ *   -1 on invalid parameter
+ *   Otherwise, return value from rte_eal_init()
+ */
+__rte_experimental
+int
+rte_args_eal_init(struct rte_args *a);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* RTE_EAL_ARGS_H */
diff --git a/lib/args/version.map b/lib/args/version.map
new file mode 100644
index 0000000000..776bd22b82
--- /dev/null
+++ b/lib/args/version.map
@@ -0,0 +1,19 @@
+DPDK_24 {
+	local: *;
+};
+
+EXPERIMENTAL {
+	global:
+
+	# added in 24.03
+	rte_args_add;
+	rte_args_add_argv;
+	rte_args_add_list;
+	rte_args_alloc;
+	rte_args_create;
+	rte_args_eal_init;
+	rte_args_free;
+	rte_args_get_argc;
+	rte_args_get_argv;
+	rte_args_has_arg;
+};
diff --git a/lib/meson.build b/lib/meson.build
index 6c143ce5a6..7049a4b00e 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -13,6 +13,7 @@ libraries = [
         'kvargs', # eal depends on kvargs
         'telemetry', # basic info querying
         'eal', # everything depends on eal
+        'args',
         'ring',
         'rcu', # rcu depends on ring
         'mempool',
@@ -76,6 +77,7 @@ if is_ms_compiler
 endif
 
 optional_libs = [
+        'args',
         'bbdev',
         'bitratestats',
         'bpf',
