Resumen del manejo de argumentos de línea de comandos en C/C++
Hace un tiempo, al revisar el código del núcleo de Linux, vi cómo se manejan los parámetros de módulo (moduleparam) y me pareció bastante ingenioso. No pude evitar pensar en cómo manejar mejor los parámetros de línea de comandos en C. El código utilizado en este artículo está aquí aparsingEl código es compatible con Windows, Linux y Mac OS X para compilar y ejecutar. Las instrucciones detalladas de compilación se encuentran en el archivo README.md.
getenv
La biblioteca estándar nos proporciona una función getenv
, que literalmente se utiliza para obtener variables de entorno. Así que, siempre que configuremos previamente las variables de entorno necesarias, podemos sacarlas dentro del programa y, de este modo, pasarlas indirectamente como parámetros al programa. Veamos el siguiente códigoLo siento, no hay contenido para traducir.
La declaración de la función getenv
se encuentra en la referencia 4Toma como entrada el nombre de la variable que desea obtener y devuelve el valor de esa variable; si la variable no se encuentra, devuelve 0. 10和 15La acción consiste en obtener los valores de dos variables de entorno, y si las variables son válidas, se imprimen sus valores. Es importante notar que getenv
devuelve todos los valores como cadenas de texto, lo que requiere que el usuario convierta manualmente los tipos numéricos, por lo que su uso no es del todo conveniente. Compilar y ejecutar:
Bajo Windows:
Linux 下:Linux operating system:
Documentos:
getopt
Linux nos proporciona un conjunto de funciones getopt, getopt_long, getopt_long_only
para manejar los argumentos pasados a través de la línea de comandos. Las declaraciones de estas tres funciones son:
getopt
solo puede manejar parámetros cortos (es decir, parámetros de un solo carácter), mientras que getopt_long
y getopt_long_only
pueden manejar parámetros largos. Para una explicación detallada de las funciones, puedes consultar el manual en Linux. A continuación, explicaremos el uso de getopt
y getopt_long
a través de ejemplos.
Es importante tener en cuenta que estas funciones no están disponibles en Windows, así que busqué una versión del código fuente que pueda compilar en Windows y realicé algunas modificaciones menores. Puedes encontrar todo el código aquíLo siento, no hay texto para traducir. ¿Puedo ayudarte con algo más?
Vamos a analizar en detalle el uso de getopt_long
. Los tres primeros parámetros de getopt_long
son los mismos que los de getopt
, que son: el número de parámetros de la línea de comandos argc
, el arreglo de parámetros de la línea de comandos argv
, y la forma específica de los parámetros cortos optstring
. El formato de optstring
consiste en caracteres de parámetros cortos, donde un dos puntos :
indica que lleva parámetro, y dos puntos ::
indican que el parámetro es opcional. Por ejemplo, en la línea 19, se declara la forma de los parámetros cortos: el parámetro b
no lleva parámetros adicionales, el parámetro a
lleva un parámetro adicional, y c
lleva un parámetro opcional.
Los dos últimos parámetros de getopt_long
se utilizan para gestionar los argumentos largos, siendo la estructura de option
:
struct option {
const char *name; // Nombre de parámetro largo
int has_arg; // Si se requieren parámetros adicionales
int *bandera; // Establecer cómo devolver el resultado de la llamada a la función
int val; // Valor devuelto
};
name
aún puede ser un solo carácter.
El has_arg
puede establecerse como no_argument, required_argument, optional_argument
, que respectivamente indican sin argumento, con argumento, con argumento opcional.
flag
y val
se utilizan en conjunto; si flag = NULL
, getopt_long
devolverá directamente val
. De lo contrario, si flag
es un puntero válido, getopt_long
realizará una operación similar a *flag = val
, estableciendo el valor de la variable a la que apunta flag
como el valor de val
.
Si getopt_long
encuentra un parámetro corto coincidente, devolverá el valor de carácter de ese parámetro corto; si encuentra un parámetro largo coincidente, devolverá val
(si flag = NULL
) o devolverá 0
(si flag != NULL; *flag = val;
); si se encuentra un carácter que no es un parámetro, devolverá ?
; si se han procesado todos los parámetros, devolverá -1
.
Utilizando las características del valor de retorno, podemos lograr un efecto en el que los parámetros largos y cortos tienen el mismo significado. Por ejemplo, en el primer parámetro add
de long_options
, si el valor val
se establece como el carácter del parámetro corto 'a'
, entonces al evaluar el retorno, --add
y -a
entrarán en la misma rama de procesamiento y se tratarán como significados equivalentes.
La última pieza del rompecabezas es el uso de optind
y optarg
. optind
indica la posición del siguiente parámetro a procesar en argv
, mientras que optarg
apunta a la cadena del argumento adicional.
Compilar y ejecutar el código:
$ .\getopt_test -a 1 -b -c4 --add 2 --verbose --verbose=3 -123 -e --e
option a with value '1'
option b
option c with value '4'
option a with value '2'
option verbose
option verbose with arg 3
option 1
option 2
option 3
.\getopt_test: invalid option -- e
.\getopt_test: unrecognized option `--e'
El significado de -a
y --add
es el mismo; los parámetros opcionales de los parámetros cortos se colocan directamente después, por ejemplo -c4
, mientras que los parámetros opcionales de los parámetros largos deben tener un signo de igual, por ejemplo --verbose=3
.
mobuleparam
Vale, finalmente llegamos al método que inició este artículo. El núcleo de Linux utiliza un enfoque muy ingenioso para pasar parámetros a los módulos del núcleo, y ese enfoque es 'moduleparam'. Aquí explicaré brevemente cómo funciona 'moduleparam' en el núcleo de Linux, pero para obtener una explicación más detallada, recomendamos revisar el código. Aunque me he inspirado en algunos métodos de manejo de 'moduleparam', hay algunas diferencias con respecto al 'moduleparam' del núcleo de Linux. Para distinguirlos, voy a llamar a mi método 'small moduleparam', mientras que el del núcleo de Linux seguirá denominándose 'moduleparam'.
Primero, echemos un vistazo al uso de moduleparam
, declarado dentro del módulo:
Luego de cargar los módulos, ingresar los parámetros:
La variable enable_debug
se ha configurado correctamente como 1
, lo cual simplifica su uso. Requiere muy pocas líneas de código adicionales, lo que permite escribir un código conciso y elegante. No es necesario recurrir a bucles de control extensos como en los casos de getenv
y getopt
, además proporciona conversión de tipos incorporada. Por lo tanto, considero que sería muy útil utilizar este enfoque para manejar los argumentos de la línea de comandos.
A continuación, veamos la implementación central de moduleparam
:
module_param
es un macro que en realidad crea una estructura kernel_param
que puede reflejarse en la variable proporcionada. Esta estructura almacena suficiente información para acceder y configurar la variable, es decir, en las líneas 20-24, y coloca la estructura en una sección llamada __param
(__section__ ("__param")
). Una vez que la estructura está bien definida, el núcleo, al cargar el módulo, determina la ubicación de la sección __param
del archivo elf y la cantidad de estructuras, y luego, según el nombre y param_set_fn
, configura el valor de cada parámetro. La forma de encontrar la sección con un nombre específico es dependiente de la plataforma; la implementación en el núcleo de Linux se basa en el procesamiento de archivos elf. Linux proporciona el comando readelf
para ver la información de los archivos elf, y aquellos interesados pueden consultar la información de ayuda de readelf
.
En lo anterior se mencionó que el enfoque del núcleo de Linux es específico de la plataforma, mientras que yo busco un método que sea independiente de la plataforma para manejar parámetros. Por lo tanto, debemos modificar un poco el enfoque original de moduleparam
, eliminando la declaración __section__ ("__param")
, ya que no queremos complicarnos leyendo la section
de un archivo elf. Primero echemos un vistazo al uso modificado:
Entonces, para preservar cada estructura de reflexión, agregué un macro init_module_param(num)
para declarar el espacio de almacenamiento de la estructura, donde num
es el número de parámetros. Si el número real de parámetros declarados supera num
, el programa activará un error de aserción. La declaración de module_param
es ligeramente diferente a la original, eliminando el último parámetro que representa el permiso de acceso, sin control de permisos. Además, se agregó el macro module_param_bool
para manejar variables tipo bool
, algo que no es necesario en las versiones de Linux, ya que utiliza la función integrada de gcc __builtin_types_compatible_p
para determinar el tipo de variable. Lamentablemente, MSVC no tiene esta función, por lo que tuve que quitar esta funcionalidad y agregar un macro. module_param_array
y module_param_string
son para el tratamiento de matrices y cadenas, funcionalidades que ya estaban presentes en la versión original.
Una vez declarados los parámetros, es hora de manejar los parámetros entrantes. Usa la macro parse_params
, con argc, argv
como argumentos. El tercer parámetro es un puntero a una función de callback para manejar los parámetros desconocidos. Puedes pasar NULL
para interrumpir el procesamiento de argumentos y devolver un código de error.
Compilar y ejecutar el código:
.\moduleparam_test.exe error=0 test=101 btest=1 latest=1,2,3 strtest=\"Hello World!\"
Parsing ARGS: error=0 test=101 btest=1 latest=1,2,3 strtest="Hello World!"
find unknown param: error
test = 101
btest = Y
latest = 1,2,3
strtest = Hello World!
Se puede ver que los números, arrays y cadenas se pueden leer y convertir de forma correcta. Si se encuentra un parámetro que no se puede convertir, se devolverá un código de error y se imprimirá información relacionada. Podemos agregar unas pocas líneas de código de manera sencilla para completar la lectura y el procesamiento de parámetros, lo que resulta muy elegante. Para una implementación más detallada, se puede consultar el código aquí。
Resumen
Esta vez hemos hecho un resumen de tres métodos para manejar los argumentos de la línea de comandos en C/C++, que son getenv
, getopt
y moduleparam
. Cada método tiene sus propias características, por lo que en el futuro se puede elegir el método adecuado según las necesidades reales.
La función getenv
está soportada de forma nativa en múltiples plataformas y se puede utilizar directamente, pero es bastante primitiva y utiliza variables de entorno, lo que puede contaminar el entorno. Antes de cada uso, es mejor limpiar variables de entorno innecesarias para evitar que la configuración anterior se quede y cause contaminación.
getopt
is natively supported on the Linux platform, but not on Windows, so it requires including implemented code to be used across platforms. The parameter passing conforms to the command-line parameter standard on Linux, supports optional parameters, but it is a bit cumbersome to use. Usually, it requires looping and conditional statements to handle different parameters and is not user-friendly for numerical data types.
El moduleparam
es una herramienta de procesamiento de parámetros de línea de comandos inspirada en la implementación de moduleparam
del kernel de Linux. Es fácil de usar, soporta diferentes plataformas y puede convertir fácilmente parámetros de distintos tipos. La desventaja es que requiere una variable correspondiente para cada parámetro.
Original: https://wiki.disenone.site/es
This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.
Visitors. Total Visits. Page Visits.
Este mensaje ha sido traducido utilizando ChatGPT. Por favor, comparte tus comentariosSeñale cualquier omisión.