Tengo el siguiente script bash que genera dos números aleatorios en la misma línea.
#!/bin/bash
for i in 1 2; do
unset var
until [ "$var" -lt 10000 ] 2>/dev/null; do
var="$RANDOM"
done
printf "%s," "${var/%,/}"
done
La salida es:
5751,2129,
Estoy tratando de deshacerme de la coma al final de $var
la cual probé "${var/%,/}"
para que la salida de $var = 5751,2129 pueda usarse. ¿Alguien puede ayudarme con esto?
Respuesta1
Después de asignar así: var="$RANDOM"
, la var
variable contiene una cadena de la expansión de $RANDOM
. En Bash $RANDOM
se expande a un número decimal desde el rango de 0
hasta 32767
. No hay ningún ,
personaje allí, por lo que no tiene sentido intentar eliminarlo ,
de la expansión $var
posterior.
Los caracteres de coma que observó en el resultado provienen de esta parte del código:
printf "%s," "${var/%,/}"
# here ^
Este comando se llamó en cada iteración del bucle, por lo que cada iteración agregó un carácter de coma a la salida.
Lo que se ha impreso no se puede desimprimir. Hay matices:
- Puede canalizar la salida a un filtro y el filtro puede eliminar algunas partes del mismo. El filtro puede estar fuera del script (por ejemplo, llamas
the_script | the_filter
); o puedes canalizar la salida de algunospartedel script a un filtro dentro del script. En el último caso se filtrará la salida de todo el script; aun así, lo que ha sido impreso por la parte de la escritura no queda sin imprimir; lo digo en seriohacellegar al filtro. El filtro lo elimina más tarde. - Si imprime en una terminal, es posible hacer que sobrescriba algunas partes de la salida anterior con datos nuevos. Hay personajes y secuencias para mover el cursor; Aún así todos llegan a la terminal. Puede ocultar visualmente la salida anterior casi de inmediato, pero si redirige la salida a un archivo (o a un terminal que no comprende las secuencias utilizadas), encontrará que todo está allí.
La forma correcta de deshacerse de la coma no deseada es, en primer lugar, no imprimirla; o para filtrarlo dentro del script. Hay varias formas de hacerlo y no es mi intención encontrarlas todas. Hablaré de algunos de ellos. Supongo que desea conocer posibles enfoques también para bucles con más de dos iteraciones; tal vez bucles for donde el número de iteraciones no se conoce de antemano; tal vez bucles for no enumerados por números; tal vez para bucles que quizás nunca terminen (por ejemplo, while true
en lugar de for
).
Nota: usó printf "%s," "${var/%,/}"
, no imprime ningún carácter de nueva línea al final. Intentaré replicar este comportamiento si es posible.
Algunos enfoques posibles:
El interior de tu bucle no depende de
$i
. Puede deshacerse del bucle y utilizar dos variables separadas:unset var1 until [ "$var1" -lt 10000 ] 2>/dev/null; do var1="$RANDOM" done unset var2 until [ "$var2" -lt 10000 ] 2>/dev/null; do var2="$RANDOM" done printf '%s,%s' "$var1" "$var2"
Notas:
- Que no esSECO.
- No escala bien. (¿Y si lo hubieras hecho
for i in {1..100}
?) - Resulta engorroso si no se conoce de antemano el número de iteraciones.
Puede poner su código actual en una tubería y filtrar la coma final. Ejemplo:
for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s," "$var" done | sed 's/,$//'
Notas:
sed
(o cualquier filtro que utilice) puede o no manejar la línea incompleta (una línea que no termina con el carácter de nueva línea) que produce el bucle. En caso desed
que dependa de la implementación.- Si
sed
lo maneja, aún puede terminar su salida con una nueva línea. sed
(o cualquier filtro que utilice) puede no manejar una línea que sea demasiado larga. El código particular anterior genera una línea razonablemente corta, pero en general (imagine muchas iteraciones) la longitud puede ser un problema.sed
, como herramienta orientada a líneas, debe leer una línea completa antes de procesarla. En este caso debe leer toda su entrada. No obtendrá nada de él hasta que finalicen todas las iteraciones.- El bucle se ejecuta en una subcapa. En un caso general, es posible que desee que actúe en el shell principal, por cualquier motivo.
Puede capturar el resultado de su código actual en una variable. Al final, elimina la coma final mientras expande la variable:
capture="$(for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s," "$var" done)" printf "%s" "${capture%,}"
Notas:
- El bucle se ejecuta en una subcapa. En un caso general, es posible que desee que actúe en el shell principal, por cualquier motivo.
- El código permanece en silencio hasta que llega al último
printf
(fuera del bucle). No obtendrás nada hasta que finalicen todas las iteraciones. - En general, un bucle puede generar cualquier número de bytes. Creo que Bash puede almacenar una cantidad significativa de datos en una variable; entonces, como
printf
es incorporado, puedeprobablementemanejarprintf "%s" "${capture%,}"
sin golpearel límite de longitud de una línea de comando. No he probado esto a fondo porque, en mi opinión, almacenar una gran cantidad de datos en una variable de shell no es la mejor práctica de todos modos. Aun así, la práctica puede estar justificada si sabes que el resultado tiene una duración limitada. (Para que conste: el código anterior genera una salida muy breve con seguridad). - Bash no puede almacenar caracteres NUL en una variable (la mayoría de los shells no pueden; zsh sí). Además,
$()
elimina todas las nuevas líneas finales. Esto significa que no puede utilizar una variable para almacenar unarbitrariogenerarlo y reproducirlo más tarde con precisión. (Para que conste: en el código anterior, el fragmento interior$()
no genera NUL ni nuevas líneas finales).
En lugar de capturar la salida, puedes hacer que cada iteración se agregue a alguna variable:
capture='' for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done capture="$capture$var," done printf "%s" "${capture%,}"
Notas:
- El código se ejecuta en el shell principal (no en un subshell).
- Aún se aplican las limitaciones de almacenamiento de datos en una variable (consulte el método anterior).
- En Bash puedes agregar a la variable con
capture+="$var,"
. (Nota: si el atributo de número entero se ha establecido para la variable, entonces=+
significa "añadir", no "añadir".)
Puede detectar la última iteración y utilizar un formato sin
,
:# this example is more educative with more than two iterations for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done if [ "$i" -eq 5 ]; then printf "%s" "$var" else printf "%s," "$var" fi done
Notas:
- Sin subcapa.
- Detectar la última iteración es más difícil si no se conoce el número de antemano.
- Es aún más difícil si itera sobre una matriz (por ejemplo
for i in "${arr[@]}"
). - Cada iteración se imprime inmediatamente y el resultado se obtiene de forma secuencial. Esto funcionaría incluso si el bucle fuera infinito.
Puede detectar la primera iteración y utilizar un formato sin
,
. Tenga en cuenta que podría haber utilizado,%s
en lugar de%s,
en su código original; entonces obtendrías,5751,2129
en lugar de5751,2129,
. Con este cambio, cualquiera de los métodos anteriores que evita o elimina la coma final se puede convertir en un método que evita o elimina la coma inicial. El último método se convierte en:# this example is more educative with more than two iterations for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done if [ "$i" -eq 1 ]; then printf "%s" "$var" else printf ",%s" "$var" fi done
Notas:
- Sin subcapa.
- Detectar la primera iteración es fácil si siempre comienza con
1
(o con una cadena única fija en general). - Pero es más difícil si itera sobre una matriz, por ejemplo
for i in "${arr[@]}"
. No debes verificarif [ "$i" = "${arr[1]}" ]
porque puede haber un elemento idéntico al"${arr[1]}"
siguiente en la matriz. Una forma sencilla de lidiar con esto es mantener un índice para el bucle (index=1
antes del bucle, luego incrementarlo en uno al final de cada iteración) y probar su valor1
; Sin embargo, ese código me parece algo engorroso. - Cada iteración se imprime inmediatamente y el resultado se obtiene de forma secuencial. Esto funcionaría incluso si el bucle fuera infinito.
Puedes hacer
,
que provenga de una variable. Ingrese al bucle con la variable vacía y configúrelo,
al final de cada iteración. Esto efectivamente cambiará el valor solo una vez al final delprimeroiteración. Ejemplo:# this example is more educative with more than two iterations separator='' for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s%s" "$separator" "$var" separator=, done
Notas:
- Sin subcapa.
- Funciona bien incluso si se itera sobre una matriz.
- Cada iteración se imprime inmediatamente y el resultado se obtiene de forma secuencial. Esto funcionaría incluso si el bucle fuera infinito.
Personalmente encuentro este método bastante elegante.
Notas generales:
- Cada fragmento genera una salida sin una nueva línea final (con una posible excepción del que tiene
sed
u otro filtro). Si necesita que toda la salida forme una línea de texto terminada correctamente, ejecuteprintf '\n'
(o soleecho
) después del bucle. - Quieres
,
ser un separador, no un terminador. Esto significa que un bucle con exactamente cero iteraciones generará la misma salida vacía que un bucle con exactamente una iteración, si$var
en la iteración se expande a una cadena vacía. En nuestro caso,$var
se expande a una cadena no vacía cada vez y sabemos que tenemos más de cero iteraciones; pero en un caso general el uso de un separador en lugar de un terminador puede generar ambigüedad.