Centralizamos los comentarios sobre las implementaciones en los Makefiles, para separar la explicación de la implementación en si
DOC_COMANDOS_LINUX = $(wildcard doc/*.sh)
# Problemas con éste target:
#
# 1. al utilizar el símbolo ; dentro del foreach se ejecutará en cada iteración
# y al igual que en la terminal de linux, hará que cada comando se ejecute de forma independiente
#
# 2. lo anterior es problema suponiendo que queremos transformar la información generada
# (Ej. agregandole una cabecera)
#
imprimir-comando-descripcion:
$(foreach comando, $(DOC_COMANDOS_LINUX), cat $(comando) | sed -n '1p';)
DOC_COMANDOS_LINUX = $(wildcard doc/*.sh)
# Problemas con estos targets:
#
# 1. estamos creando un archivo comandos-linux.txt cada vez que ejecutamos imprimir-comandos,
# es decir no comprobamos si se actualizaron ó no los archivos contenidos en $(DOC_COMANDOS_LINUX)
#
# 2. no estamos aprovechando la comparación del timestamp entre el target y las dependencias para crear archivos de GNU Make,
# la manera más común sería una regla como la siguiente.. archivo-transformado.txt: archivo1.txt archivo2.txt archivo3.txt
#
imprimir-comandos: comandos-linux.txt
comandos-linux.txt:
$(foreach comando, $(DOC_COMANDOS_LINUX), cat $(comando) | sed -n '1p';)
DOC_COMANDOS_LINUX = $(wildcard doc/*.sh)
# Nota: $(patsubst pattern, replacement, text)
# Problemas:
# 1. NO era necesario utilizar la función patsubst, sólo necesitabamos usar la función basename de GNU Make
# 2. Si bien le ponemos "WRONG", éste método funciona pero se pierde expresividad y es más dificil de mantener
COMANDOS_LINUX_WRONG = $(notdir $(patsubst %.sh,%,$(DOC_COMANDOS_LINUX)))
COMANDOS_LINUX = $(basename $(notdir $(DOC_COMANDOS_LINUX)))
DOC_COMANDOS_LINUX = $(wildcard doc/*.sh)
DOC_SHORTCUTS_LINUX = $(wildcard doc/*.org)
# Nota: $(filter-out pattern, text)
#
# 1. Filtramos de (A) los elementos (a) que no estén incluidos en otra lista (B)
#
COMANDOS_LINUX_WITHOUT_SHORTCUTS = $(filter-out $(SHORTCUTS_LINUX),$(COMANDOS_LINUX))
Posible Problema:
- al ejecutar el binario el emulador de terminal diga
Command 'bat' not found
Solución:
- crear un softlink de
/usr/bin/batcat
en ~~/.local/bin/bat~
# suponiendo que no existe el directorio
mkdir -p ~/.local/bin
# creamos el softlink /usr/bin/batcat en ~/.local/bin/bat
ln -s /usr/bin/batcat ~/.local/bin/bat
Según la que eligamos dará problemas ó no cuando les asignamos un comando de shell..
Si es una MACRO RECURSIVA, entonces utilizamos el símbolo de asignación
=
Por ejemploarchivo-especifico = $(shell ls *.md | grep '^READ')
- PROBLEMA 1: GNU Make lo evaluará y expandirá cada vez que se referencie en el Makefile
- PROBLEMA 2: ejecuta el comando a cada rato, en lugares dónde aún no queríamos que se ejecutara (Ej. en un ifeq)
Si es una MACRO SIMPLE, entonces utilizamos el símbolo de asignación
:=
- Por ejemplo
archivo-especifico := $(shell ls *.org | grep 'TODO\.org$')
- PROBLEMA 1: GNU Make lo evaluará varias veces pero se expandirá una única vez (la primera vez que se referenció)
- PROBLEMA 2: ejecutará apenas ejecutamos el Makefile (peor que antes)
# a diferencia de las alternativas de abajo, ésta si funciona y evitamos los problemas de expansión no deseada de GNU Make
POPUP_EDIT = sh ./scripts/edit-popup.sh
# Notas:
# - en todas las siguientes asignaciones causarán problemas cuando GNU Make las evalué y expanda
# - de igual forma las comento por el tema de que cada linea en una regla de Makefile se ejecuta en una shell diferente
# 1. alternativa a la función shell sería usando el símbolo !=
# 2. en éste ejemplo considero que queda mejor usar shell para no confundir y no olvidar que el comando read es propio de linux
POPUP_EDIT != read -p "Si desea editar comandos escriba sin paréntesis (c) y para shortcuts (s): " popup_edit; echo $$popup_edit
#
# 1. nuestro comando shell imprimirá un string que se asignará a la macro POPUP_EDIT
# 2. los comandos de linux se ejecutarán en la misma shell, por tanto se perderá el valor si intentamos imprimir $$popup_edit en otra macro ó target
POPUP_EDIT ?= $(shell read -p "Desea editar comandos escriba (c) para shortcuts (s): " popup_edit; echo $$popup_edit)
# 1. también sólo podriamos asignar los comandos a ejecutar como un string
# 2. para no perder el valor de popup_edit podríamos utilizar el operador && para manternos en la misma shell así $(POPUP_EDIT) && echo $$popup_edit
# pero... no podriamos utilizarlo en un ifeq de GNU Make
POPUP_EDIT = read -p "Si desea editar comandos escriba sin paréntesis (c) y para shortcuts (s): " popup_edit; echo $$popup_edit
- Prompt user input makefile (nono.ma)
- makefile why is the read command not reading the user input (stackoverflow.com)
- How can I pass a command line argument into a shell script (unix.stackexchange.com)
- Oneshell not working properly in makefile (stackoverflow.com)
Podemos usar el comando
test -f archivo
para verificar si existe con los operadores lógicos&&
y||
- la expresión seguida del operador lógico AND
&&
se evalúa si existe el archivo- la expresión seguida del operador lógico OR
||
se evalúa si NO existe el archivoó podemos usar el condicional
if [ -s archivo ]
sin olvidar el uso de los;
- usar
;
al final de cada expresión delimitada dentro de la condiciónif
ó delelse
- finalizar el bloque
if
con unfi
doc/archivo.txt: .tmp/archivo-4.txt .tmp/archivo-1.txt .tmp/archivo-9.txt
@test -f $@ \
&& echo "existe el archivo..!" \
|| echo "no existe el archivo, ejecutar algún comando que procese los archivos dependencia y cree el archivo"
doc/otro-archivo.txt: .tmp/archivo-1.txt .tmp/archivo-2.txt .tmp/archivo-3.txt
@if [ -s $@ ]; then \
echo "existe el archivo"; \
else \
echo "no existe el archivo, ejecutar algún comando que procese los archivos dependencia y cree el archivo"; \
fi
- How do I check if file exists in makefile (stackoverflow.com)
- How to write multiple conditions in makefile (stackoverflow.com)
Generamos un archivo
comandos-linux.txt
en base al contenido de sus dependencias (los .sh)
- siempre que en una regla de Makefile haya una diferencia en el timestamp entre el TARGET y sus DEPENDENCIAS
- con la diferencia de timestamp, GNU Make nos permitirá ejecutar la orden asociada a la creación del TARGET
- creamos el archivo (target) cuando los archivos de los que depende (sus dependencias) tienen una fecha de modificación más reciente
# - truncamos el archivo, lo modificamos el tamaño del archivo a cero Bytes
# - todos los datos que tenía se pierden
archivo.txt:
@truncate -s 0 $@
# - iteramos sobre cada dependencia del target
# - la macro especial $^ obtiene todas las dependencias
# - la función foreach es de la forma (foreach elemento-de-la-lista, lista-de-archivos, orden-a-ejecutar)
# - para utilizar el elemento de la iteración del foreach dentro de la orden que ejecuta, debemos hacerlo como una macro $(elemento-de-la-lista)
@$(foreach comando, $^, echo $(comando))
# obtenemos una linea en particular del archivo (la descripción, en la primera linea)
cat archivo.txt | sed -n '1p'
# agregamos un string al principio de cada linea
cat archivo.txt | nawk '{print "$(basename $(notdir $(comando))) " $$0}'
# - reemplazamos los símbolos no deseados por ejemplo ~#~ por otro por ej. el pipe ~|~
# - escapamos los símbolos usando el slash invertido
cat archivo.txt | sed 's/\#/\|/g'
# - agregamos el texto transformado en el target (el archivo comandos-linux.txt)
# - el operador de redirección ~>>~ que agrega contenido (si usaramos ~>~ borraría el contenido anterior)
# - la macro especial ~$@~ que obtiene el nombre del target
archivo.txt:
echo "texto transformado" >> $@
comandos-linux.txt: doc/ls.sh doc/mv.sh doc/tar.sh doc/curl.sh
@$(TRUNCATE_CLEAR_CONTENT) $@
@$(foreach comando, $^,\
cat $(comando) | sed -n '1p' | nawk '{print "$(basename $(notdir $(comando))) " $$0}' | sed 's/\#/\|/g' \
>> $@;\
)
comandos-linux.txt: doc/ls.sh doc/mv.sh doc/tar.sh doc/curl.sh
@$(TRUNCATE_CLEAR_CONTENT) $@
@$(foreach comando, $^,\
cat $(comando) | \
sed -n '1,2p' | \
nawk 'BEGIN{print "$(basename $(notdir $(comando)))|" } {print $$0}' | \
sed -E 's/\#\# (CATEGORIA)\: (([[:alnum:]]|[[:space:]]|[[:punct:]])+)$$/\2\|/g' | \
sed -E 's/\#\# (DESCRIPCION)\: (([[:alnum:]]|[[:space:]]|[[:punct:]])+)$$/\2;/g' | \
tr --delete '\n' | tr ';' '\n' | \
nawk -F '|' '{print $$1 " | " toupper($$2) " | " toupper(substr($$3,1,1)) substr($$3,2)}' \
>> $@;\
)
# extraemos sólo las lineas 1 y 2 del archivo
cat archivo.txt | sed -n '1,2p'
# - agregamos al principio de cada linea un texto, en éste caso la macro comando
# - la función notdir obtiene el nombre del archivo (con la extensión, usaremos basename para removerla)
# - la función basename obtiene el nombre del archivo sin la extensión
# - usamos $$0 en vez de $0 para que GNU Make no la tome como una macro de Makefile si que la escape é interprete un caracter y sea un parámetro del comando awk
cat archivo.txt | nawk 'BEGIN{print "$(basename $(notdir $(comando)))|" } {print $$0}'
# - en las regex es típico usar paréntesis para los Grupos de Captura (Capture Group) es decir son "Construcciones de Agrupamiento de Expresiones Regulares"
# - en awk obtenemos el contenido de los Grupos de Captura con \1 \2 .. \n siendo n el número del grupo de captura del que queremos el contenido
cat archivo.txt | sed -E 's/\#\# (CATEGORIA)\: (([[:alnum:]]|[[:space:]]|[[:punct:]])+)$$/\2\|/g'
# - al final de cada columna de awk le agregamos un símbolo ; como centinela para detectar el fin de linea
cat archivo.txt | sed -E 's/\#\# (DESCRIPCION)\: (([[:alnum:]]|[[:space:]]|[[:punct:]])+)$$/\2;/g'
# borramos los saltos de linea
cat archivo.txt | tr --delete '\n'
# reemplazamos los ; por saltos de linea (antes los habíamos agregado para diferenciar el fin de linea)
cat archivo.txt | tr ';' '\n'
# - indicamos que el separador de las columnas en el símbolo | (el pipe)
# - transformamos el texto manipulando el orden de las columnas $1 $2 $3 detectadas por awk
# (recordando que agregamos un $ al principio $$1 $$2 .. porque si nó GNU Make lo tomará como una macro, y queremos que lo tome como un caracter)
cat archivo.txt | nawk -F '|' '{print $$1 " | " toupper($$2) " | " toupper(substr($$3,1,1)) substr($$3,2)}'
- Grupos de captura en JavaScript (w3.unpocodetodo.info)
- POSIX, Cuantificadores, Puntos de anclaje, Sustituciones, Escape de Caracteres (bioinf.comav.upv.es)
- sed commands in linux (linuxteck.com)
- using awk to set first character to lowercase unix (stackoverflow.com)
- how can I use as an awk field separator (stackoverflow.com)
- how can I replace each newline n with a space using sed command (stackoverflow.com)
- what is a non capturing group in regular expressions (stackoverflow.com)
- how to match whitespace in sed command (superuser.com)
- Interactuamos con el usuario mediante un cuadro de diálogo
- Usaremos el programa
whiptail
para la interfáz del cuadro de diálogo- Redireccionamos el stderr al stdout para capturar lo que escribe
whiptail
- Capturamos el valor de salida de
whiptail
conxargs
para crear nuevos archivos a partir de otros (templates)
# alternativa al `rsync --ignore-existing` sería `cp --no-clobber`
COPY_NOT_OVERWRITE=rsync --ignore-existing
DOC_COMMANDS_LINUX_DIRECTORY=doc/linux-shell/commands
DOC_SHORTCUTS_LINUX_DIRECTORY=doc/linux-shell/shortcuts
MENU_CREATE_DOC_LINUX= whiptail \
--inputbox "Escriba el nombre del comando" 25 80 \
--title "Crear Documentación de Linux"
linux-create-doc:
$(MENU_CREATE_DOC_LINUX) 3>&1 1>&2 2>&3 \
| xargs -I % sh -c "\
$(COPY_NOT_OVERWRITE) $(DOC_COMMANDS_LINUX_DIRECTORY)/.template $(DOC_COMMANDS_LINUX_DIRECTORY)/%.sh; \
$(COPY_NOT_OVERWRITE) $(DOC_SHORTCUTS_LINUX_DIRECTORY)/.template $(DOC_SHORTCUTS_LINUX_DIRECTORY)/%.org; \
echo 'Se creó el archivo $(DOC_COMMANDS_LINUX_DIRECTORY)/%.sh;' \
echo 'Se creó el archivo $(DOC_SHORTCUTS_LINUX_DIRECTORY)/%.org'; \
"
- Capturamos la opción elegida del menu de
whiptail
conxargs
- La opción capturada será el nombre del
target
que pasaremos por parámetro al mismoMakefile
para ejecutar- Con
$(MAKE)
se invoca a si mismo el Makefile
# Notas:
# 1. es necesario definir los targets linux-create-doc y app-create-doc
# 2. al confirmarle a whiptail una opción, la enviará al stdout (file descriptor 1, la pantalla)
# y GNU Make interpretará que el resultado es el nombre de un target
# (si no existen esos targets GNU Make lanzará un error de que deben definirse en el Makefile)
MENU_CREATE_DOC=whiptail \
--title "Crear documentación" \
--menu "Elegir una opción" 30 80 5 \
"linux-create-doc" "Documentación de Linux" \
"app-create-doc" "Documentación de una Aplicación"
create-doc:
$(MENU_CREATE_DOC) 3>&1 1>&2 2>&3 \
| xargs -I % $(MAKE) --no-print-directory %
linux-create-doc:
read -p "Nombre del Comando de Linux a documentar: " NOMBRE \
&& echo $(DOC_COMMANDS_LINUX_DIRECTORY)/$$NOMBRE.$(DOC_COMMANDS_EXTENSION) \
&& echo $(DOC_SHORTCUTS_LINUX_DIRECTORY)/$$NOMBRE.$(DOC_SHORTCUTS_EXTENSION)
# otra alternativa
linux-create-doc:
printf "Nombre del Comando de Linux a documentar: " && read NOMBRE \
&& echo $(DOC_COMMANDS_LINUX_DIRECTORY)/$$NOMBRE.$(DOC_COMMANDS_EXTENSION) \
&& echo $(DOC_SHORTCUTS_LINUX_DIRECTORY)/$$NOMBRE.$(DOC_SHORTCUTS_EXTENSION)
El comando
xargs
requiere utilizarlo la formaxargs -I % sh -c 'comando1 %; comando2 %; ..'
cuando queremos pasar el mismo parámetro%
a multiples comandos en la misma ShellEl símbolo
%
que usamos en los nombres de los ficheros, es el parámetro capturado por el comandoxargs
con la opción-I
- el comando
whiptail
por defecto escribe sobre elstderr
(fd 2) - necesitamos redireccionar el
stderr
(fd 2) alstdout
(fd 1)
- utilizar el operador de redirección
>
de la formafile descriptor>&otro fd
- redireccionar STDERR a STDOUT y luego STDOUT a la terminal activa (la que está en uso)
Planteandolo quedaría
2>&1 > /dev/tty
2>&1
redireccionamos el STDERR (file descriptor 2) a STDOUT (file descriptor 1)> /dev/tty
redireccionamos el STDOUT a la terminal activa (la que está en uso)
- utilizar el operador de redirección
>
de la formafile descriptor>&otro fd
- crear un File Descriptor adicional que guarde la referencia del stdout
Planteandolo quedaría
3>&1 1>&2 2>&3
3>&1
creamos un nuevo File Descriptor (fd 3) que guarda la referencia del stdout (fd 1)1>&2
redireccionamos el stdout->stderr2>&3
redireccionamos el stderr->nuevo file descriptor (fd 3, el que apuntaba al stdout)
- redirecciona el STDOUT como STDIN de otro comando
- ejemplo
ls *.txt | grep ^b
(listar todos los archivos de texto plano que empiecen con la letra b)
- redirecciona el contenido de un archivo como STDIN de un comando
- ejemplo
tr 'a-z' 'A-Z' < alumnos.txt
(convertimos todos caracteres en mayúsculas)
En el posible escenario (1) planteado, que combinamos los comandos
whiptail
yread
- Redireccionamos el contenido de la terminal en uso (activa)
/dev/tty
con el operador de redirección<
- ES FUNDAMENTAL la redirección
< /dev/tty
caso contrario.. el comandoread
FALLARÁ..
Primero la interacción con
whiptail
(no es el tema de interés al menos en éste escenario)
- Utilizamos
whiptail
para mostrar una caja de dialogo- Redireccionamos los File Descriptor (STDERR -> STDOUT) para
- porque por default escriben en el STDERR (fd 2)
- utilizar la salida de
whiptail
como entrada de otro comando con el operador pipe|
- pasar por parámetro el resultado de
whiptail
al propioMakefile
con el comandoxargs
- el parámetro recibido por el
Makefile
, lo interpeta GNU Make como el nombre de un target (que debe estar definido en el Makefile)- Elegida una opción de
whiptail
, la llamada recursiva$(MAKE)
recibe el nombre del target pasado por parámetro porxargs
Segundo la interacción con el comando
read
y el operador de redireccion<
(éste era el tema de interés)
- Solicitamos por STDIN (fd 0) que ingrese el nombre del comando ó aplicación con el comando
read
- Redireccionamos el contenido de la terminal en uso (activa)
/dev/tty
con el operador de redirección<
MENU_CREATE_DOC=whiptail \
--title "Crear documentación" \
--menu "Elegir una opción" 30 80 5 \
"linux-create-doc" "Documentación de Linux" \
"app-create-doc" "Documentación de una Aplicación"
# No es necesario utilizar la opción -I, la utilizamos para asignarle un símbolo al parámetro que recibe xargs (el %)
# y que se entienda que se lo estamos pasando por parámetro al llamado recursivo $(MAKE)
create-doc:
$(MENU_CREATE_DOC) 2>&1 > /dev/tty \
| xargs $(MAKE) --no-print-directory
# alternativa al 2>&1 >/dev/tty
# create-doc:
# $(MENU_CREATE_DOC) 3>&1 1>&2 2>&3 \
# | xargs -I % $(MAKE) --no-print-directory %
# Notas:
# 1. Una alternativa a ~read -p "texto" VARIABLE~ sería ~echo "texto: " && read VARIABLE~
linux-create-doc:
read -p "Nombre del Comando de Linux a documentar: " NOMBRE < /dev/tty \
&& $(TEXT_EDITOR) $$NOMBRE.sh
app-create-doc:
read -p "Nombre de la Aplicación a documentar: " NOMBRE < /dev/tty \
&& $(TEXT_EDITOR) $$NOMBRE.org
.PHONY: create-doc linux-create-doc app-create-doc
LINUX_COMMANDS_LIST=ls ps find tee
LINUX_SHORTCUTS_LIST=neovim screen
MENU_EDIT_LINUX_COMMANDS=whiptail \
--title "Editar documentación de Linux" \
--menu "Elegir una opción" 0 0 5 $(LINUX_COMMANDS_LIST)
MENU_EDIT_LINUX_SHORTCUTS=whiptail \
--title "Editar documentación de Linux" \
--menu "Elegir una opción" 0 0 5 $(LINUX_SHORTCUTS_LIST)
linux-edit-commands: linux-shell.txt
$(MENU_EDIT_LINUX_COMMANDS) 3>&1 1>&2 2>&3 \
| xargs -I % $(TEXT_EDITOR) $(DOC_COMMANDS_LINUX_DIRECTORY)/%.$(DOC_COMMANDS_EXTENSION)
linux-edit-shortcuts:
$(MENU_EDIT_LINUX_SHORTCUTS) 3>&1 1>&2 2>&3 \
| xargs -I % $(TEXT_EDITOR) $(DOC_COMMANDS_LINUX_DIRECTORY)/%.$(DOC_COMMANDS_EXTENSION)
- Difference between /dev/tty and /dev/tty0 (baeldung.com)
- Explained: input, output and error redirection in linux (linuxhandbook.com)
- Practical examples of the read command in Linux (linuxhandbook.com)
- How linux uses dev/tty and dev/tty0 (unix.stackexchange.com)
- Why command read doesn’t work (stackoverflow.com)
- Redirect all output to file in bash (stackoverflow.com)
- Bash scripts whiptail file select (stackoverflow.com)
- Queremos pasar un parámetro a un target sin asignarle un valor
- Ejecutar en la terminal de comandos
make mkdir editar
en vez demake editar-comando=mkdir
DOC_COMMANDS_LINUX_DIRECTORY=doc/linux-shell/commands
TEXT_EDITOR=vim
# necesario para poder usar la palabra `edit` como parámetro de cualquier target
$(eval edit:;@:)
$(COMANDOS_LINUX):
# 1. validamos si pasó el parámetro "edit" en la terminal de comandos, al ejecutar make instanciando un target incluido la macro $(COMANDOS_LINUX)
ifeq (edit, $(filter edit,$(MAKECMDGOALS)))
# 1.1 abrimos el archivo en modo de edición con algún programa (Ej. vim, nano, ..)
$(TEXT_EDITOR) $(DOC_COMMANDS_LINUX_DIRECTORY)/[email protected]
# 1.2 flujo alternativo, si no pasó el parámetro "edit",
# entonces sólo imprimimos el contenido del archivo
else
cat $(DOC_COMMANDS_LINUX_DIRECTORY)/[email protected]
endif
@test -f $(DOC_COMMANDS_LINUX_DIRECTORY)/[email protected] \
&& cat $(TEXT_EDITOR) $(DOC_COMMANDS_LINUX_DIRECTORY)/[email protected]
|| true
- lo usamos ara validar existencia de archivos
- utilizado de la forma
test -f archivo.txt && echo "existe" || echo "no existe"
- Transformar el texto de un target (en este ej. es
SHORTCUTS_LINUX
)- Agregar saltos de linea (el caracter especial
\n
) con el comando de linuxsed
, porque las funciones de sustitución de GNU Make no lo hacen- Pasar por parámetro la macro con el texto transformado con
sed
al comando linuxawk
para filtrar contenido de un archivo de texto plano
Si usamos una
Macro (A)
que transforma e imprime el texto de otraMacro (B)
, y esa laMacro (A)
la utilizamos como un comando dentro de una regla para crear un Target/Objetivo, entonces GNU Make buscará en el Makefile los targets con esos nombres..Si ese no era nuestra meta y no habíamos definido targets con esos nombres, entonces GNU Make FALLARÁ diciendo que NO encontró los targets para crear el target principal (dónde se incluyó la macro)
En este ejemplo utilizamos la
Macro (A)
como parámetro de un comando de linux
DOC_COMMANDS_LINUX_DIRECTORY=doc/linux-shell/commands
DOC_SHORTCUTS_LINUX = $(wildcard $(DOC_SHORTCUTS_LINUX_DIRECTORY)/*.org)
SHORTCUTS_LINUX = $(basename $(notdir $(DOC_SHORTCUTS_LINUX)))
# Notas:
# 1. formateamos la lista de palabras, reemplazando los espacios por el pipe | como separador
# 2. permite controlar el contenido del archivo con el comando de linux AWK
SHORTCUTS_LINUX_FORMAT=$(shell echo $(SHORTCUTS_LINUX) | sed -E 's/([[:alpha:]]+) /\1|/g')
# Notas:
# 1. si usamos el operador != en vez de la función $(shell ) no cumplía su propósito el slash invertido
# 2. en véz de borrar lineas con el comando sed y su opción /d, se prefirió filtrar filas con awk que también permite patrones (más fácil)
LINUX_SHORTCUTS_LIST=$(shell cat commands-linux-shell.txt \
| awk -F '|' '{print $$1 "::" $$2}' \
| sed -E 's/([[:alpha:]]+)::(.+)/\"\1\" \"\2\"\\/g' \
| awk '/$(SHORTCUTS_LINUX_FORMAT)/' \
| tr '\\' '\n')
# el comando `tr` no cumplía su propósito a diferencia del comando `sed`
SHORTCUTS_LINUX_FORMAT_FAIL = $(shell echo $(SHORTCUTS_LINUX) | tr '[:space:]' '\|')
# el operador de redirección <<< falla
SHORTCUTS_LINUX_FORMAT_FAIL = sed -E 's/([[:alpha:]]+) /\1|/g' <<< "$(SHORTCUTS_LINUX)"
# ésta fue la macro utilizada, que no arroja errores
SHORTCUTS_LINUX_FORMAT=$(shell echo $(SHORTCUTS_LINUX) | sed -E 's/([[:alpha:]]+) /\1|/g')
- Adaptar el texto de un archivo que está de la forma
dato1|dato2|
a algo como"dato1" "dato2"
seguidos de un salto de linea\n
- Crear un menú con el comando
whiptail --menu
con el texto transformado
- Leer el contenido de un archivo de texto plano (.txt) que tiene columnas delimitadas por el símbolo pipe
|
- Cambiar el separador de columnas
|
por otro::
conawk
para facilitar la sustitución consed
- Sustituir los textos de las columnas separadas por
::
de la forma"palabra" "otra"
seguido de un slash invertido\
- Agregar saltos de linea al final de cada columna
- si utilizamos
!/patron/
enawk
, filtramos el contenido de un archivo, usando el!
como una negaciónNOT
- la solución actual con
sed
se complicaba el borrar lineas con varios patrones)
# faltan algunas macros, NO se agregaron enfocar ésta solución
DOC_LINUX=linux-shell
LINUX_COMMANDS_LIST=$(shell cat $(DOC_LINUX).txt \
| nawk -F '|' '{print $$1 "::" $$2}' \
| sed -E 's/(.+)::(.+)/"\1" "\2" \\/g' \
| tr '\\' '\n')
# Nota: Si usamos el operador != en vez de la función $(shell ) no cumplirá su propósito el slash invertido
LINUX_SHORTCUTS_LIST=$(shell cat $(DOC_LINUX).txt \
| awk -F '|' '{print $$1 "::" $$2}' \
| sed -E 's/([[:alpha:]]+)::(.+)/\"\1\" \"\2\"\\/g' \
| awk '/$(SHORTCUTS_LINUX_FORMAT)/' \
| tr '\\' '\n')
Existe un problema en las soluciones planteadas con el operador de redirección
>>
y de los comandostee
ysed
- si la última linea del archivo dónde insertarmos contenido no tiene el caracter especial
\n
de salto de linea- al insertar contenido en el archivo, éste agregará delante del texto que ya contenía.. en vez de una nueva linea
- el resultado de inserción no será el deseado, por tanto “se insertará mal”
- con
echo "texto" >> archivo.txt
agregamos contenido a un archivo - si utilizaramos el operador de redirección
>
en vez de>>
, se borraría el contenido anterior del archivo
- con
echo "texto" | tee --append archivo.txt
agregamos contenido a un archivo - el comando
tee
se diferencia de los operadores de redirección>
y>>
porque- escribe en un archivo
- imprime por pantalla (stdout, fd 1) lo que escribió en el archivo
- con sed es
sed -i '$ a texto' archivo
agregamos contenido a un archivo
CONFIRM_INSTALL=$(shell whiptail \
--title "Instalar Aplicación $(APP_NAME)" \
--yesno "¿Desea confirmar acción?" 0 0 \
--no-button "Cancelar" --yes-button "Confirmar" 3>&1 1>&2 2>&3 && echo true)
# - éste target está MAL, porque la expresión que comparamos CONFIRM_INSTALL escribe un cuadro de diálogo sobre la pantalla (stdout, fd 1)
# - el comando whiptail no devuelve un valor booleano, devuelve la interfáz de un cuadro de dialogo
install:
ifeq ($(CONFIRM_INSTALL), true)
@echo "instalando..."
else
@echo "no hago nada"
endif
Si en la terminal de comandos escribimos
make
ómake nombre-de-algun-target
- se va a evaluar cada expresión de la condición del
ifeq
de GNU Make- la macro
CONFIRM_INSTALL
se evaluará y se imprime la caja de diálogo dewhiptail
por pantalla (stdout, fd 1)- la caja de diálogo de
whiptail
aparecerá en la pantalla cada vez que se ejecute elMakefile
, aunque no instanciemos el targetinstall
..el PROBLEMA ocurre porque el comando
whiptail
- escribe en el stdout (file descriptor 1) una interfaz gráfica,
- escribe en el stderr (file descriptor 2) el resultado de la opción elegida en la interfáz gráfica
- en nuestro
ifeq
de GNU Make “creemos” que devuelve (escribe por stdout) un valor booleanotrue
ófalse
Si queremos seguir utilizando el comando
whiptail
, entonces necesitamos
- utilizar la variable
$?
que tiene el Estado de Salida (exit status) del último comando ejecutado en la Shell- escapar el símbolo
$
de$?
para que GNU Make no lo considere como una macro, si no como un caracter común- verificar el valor de
$?
con el comandotest
(en reemplazo deifeq
) de la formatest $$? -eq 0
- si
$?
devuelve0
(cero), entonces el comando tuvo éxito (eligieron la opción YES)- si
$?
devuelve distinto de0
(cero), entonces el comando NO tuvo éxito (eligieron la opción NO)Otra alternativa a
whiptail
, sería utilizar el comandoread
- Mismo problema que la solución fallida (1)
EXIT_STATUS_SUCCESS=0
CONFIRM_INSTALL=whiptail \
--yesno "¿Desea confirmar acción?" 0 0 \
--no-button "Cancelar" --yes-button "Confirmar"
# - éste target está MAL, porque la expresión que comparamos CONFIRM_INSTALL escribe un cuadro de diálogo sobre la pantalla (stdout, fd 1)
# - el comando whiptail no devuelve un valor booleano, devuelve la interfáz de un cuadro de dialogo
install:
ifeq ($(shell $(CONFIRM_INSTALL) 3>&1 1>&2 2>&3 && echo $$?), $(EXIT_STATUS_SUCCESS))
@echo "instalando..."
else
@echo "no hago nada"
endif
i install: check-installed install-utils ## Instalar aplicación
$(BOX_CONFIRM_INSTALL) \
&& test $(EXIT_STATUS) -eq $(EXIT_STATUS_SUCCESS) \
&& (echo $(BASH_ALIAS) >> $(BASH_ALIASES_FILE) \
&& chmod u+x $(UTILS_DIRECTORY)/update-bash-aliases \
&& $(UTILS_DIRECTORY)/update-bash-aliases) \
|| true
- Makefile file exists check not consistent (unix.stackexchange.com)
- ifeq conditional syntax in makefile (stackoverflow.com)
- Una macro con un comando de Shell (en este caso
grep
) que verifique si un archivo contiene un patrón- Si el patrón está en el archivo ejecutar un comando ú otro
Si una macro contiene una función
$(shell ..)
GNU Make lanzará ERROR
- si la usamos en la receta de una regla como un comando común de Shell junto con operadores lógicos
&&
y||
- si la usamos en la receta de una regla y el Makefile no tiene definido un target con el mismo nombre del resultado que devuelva
Si una macro contiene una función
$(shell ..)
podemos usarla
- como una expresión a evaluar dentro de un condicional
ifeq
(Ej.ifeq ($(EXISTE_ARCHIVO), true) ...
)- como un parámetro de un comando de Shell como test, awk, tee, ..
- Ej.
ifeq ($(APP_INSTALLED), true) ...
(suponiendo queAPP_INSTALLED
devuelve un valor booleano)- Ej.
$(LISTA_COMANDOS) | awk /$(COMANDOS_LINUX)/ | ...
(en éste caso usariamosawk
como un filtro)- Ej.
comando && test $(OTRO_COMANDO) -eq 0 && accion si tuvo exito || accion si falla ...
EXIT_STATUS=$(shell echo $$?)
EXIT_STATUS_SUCCESS=0
# - ésta macro debería ser utilizada como una expresión en un ifeq de GNU Make
# - arrojará ERROR, si la utilizamos en una regla de la forma $(APP_INSTALLED) && ..
APP_INSTALLED=$(shell grep -q "^alias ?='make.*APP_AUTHOR=neverkas" ~/.bash_aliases && echo $$?)
# éste target va a fallar, por lo dicho arriba
check-installed:
$(APP_INSTALED) \
&& test $(EXIT_STATUS) -eq $(EXIT_STATUS_SUCCESS) \
&& echo "ya está instalada" \
|| true
EXIT_STATUS= $??
# dejo comentado para que se entienda que NO siempre es necesario
# usar la función $(shell ) e imprimir un valor con el comando `echo`
#
# EXIT_STATUS=$(shell echo $$?)
EXIT_STATUS_SUCCESS=0
# esto va OK, chequeado
APP_INSTALLED=grep -q "^alias ?='make.*APP_AUTHOR=neverkas" ~/.bash_aliases
check-installed:
$(APP_INSTALLED) \
&& test $(EXIT_STATUS) -eq $(EXIT_STATUS_SUCCESS) \
&& echo "ya está instalada" \
|| true
# verificamos si el archivo .bash_aliases contiene el patrón "^alias ?='make.*APP_AUTHOR=neverkas",
# el comando grep devolverá 0 si tiene éxito y distinto de 0 si no tuvo éxito..
#
# si el comando grep devuelve 0 entonces se ejecuta el echo true
APP_INSTALLED=$(shell grep -q "^alias ?='make.*APP_AUTHOR=neverkas" ~/.bash_aliases && echo true)
# nota: se podría haber usado el test $? -eq
check-installed:
ifeq ($(APP_INSTALLED), true)
$(error La aplicación ya está instalada)
endif
- How to check if a file contains a specific string using bash (stackoverflow.com)
- Raise error in a bash script (stackoverflow.com)
PATTERN_METADATA_DATE=(\#\# ACTUALIZADO:) ([[:digit:]]{1,2}\/[[:digit:]]{1,2}\/[[:digit:]]{4})
UPDATE_METADATA_DATE=sed -i -E 's/$(PATTERN_METADATA_DATE)/\1 $(DATE_NOW_FORMAT)/i'
LINE_METADATA_OBSERVACIONES=$(shell grep -n '\#\# OBSERVACIONES:' $(DOC_COMMANDS_LINUX_DIRECTORY)/.template | cut --delimiter=':' --fields=1)
LINE_METADATA_OBSERVACIONES=$(shell grep -n '\#\# OBSERVACIONES:' $(DOC_COMMANDS_LINUX_DIRECTORY)/.template | cut --delimiter=':' --fields=1)
BAT=bat $(METADATA_OBSERVACIONES) \
--line-range $(LINE_METADATA_OBSERVACIONES):$(LINE_METADATA_OBSERVACIONES) \
--highlight-line $(LINE_METADATA_OBSERVACIONES) \
--line-range $(NUMBER_LINE_BEGIN_DOCUMENTATION):
find . -type f -delete -print
- https://askubuntu.com/questions/114969/is-there-any-faster-way-to-remove-a-directory-than-rm-rf
- https://serverfault.com/questions/319362/bash-find-command-verbose-output
ls -f | wc -l
# 1. con el comando mount listamos todos los dispositivos montados en el sistema
# 2. con grep, filtramos la ruta del celular conectado
mount | grep 'gvfsd-fuse'