¿Qué consejos generales tienes para jugar al golf en Bash? Estoy buscando ideas que se puedan aplicar a los problemas de golf de código en general que sean al menos algo específicos para Bash (por ejemplo, "eliminar comentarios" no es una respuesta). Por favor, publique un consejo por respuesta.
55
sh
y qué shell permite estafor
sintaxis? está expresamente permitido enzsh
.sh
y que Bash también lo permite por eso, aunque lamentablemente no tengo una cita.csh
, probablemente, así es como trabajaron en ese caparazón.ksh93
lo anterior podría ser:;{1..10}
y enbash
:printf %s\\n {1..10}
for((;i++<10)){ echo $i;}
es más corto quefor i in {1..10};{ echo $i;}
Para expansión aritmética, use en
$[…]
lugar de$((…))
:En expansiones aritméticas no use
$
:La expansión aritmética se realiza en subíndices de matriz indexados, por lo que no use
$
ninguno de ellos:En expansiones aritméticas no use
${…}
:fuente
while((i--))
, que funciona, conwhile[i--]
owhile $[i--]
no funcionó para mí. GNU bash, versión 4.3.46 (1)y=`bc<<<"($x*2.2)%10/1"`
... ejemplo de usobc
para cálculos no enteros ... tenga/1
en cuenta que al final trunca el decimal resultante a un int.s=$((i%2>0?s+x:s+y))
... ejemplo del uso del operador ternario en aritmética bash. Es más corto queif..then..else
o[ ] && ||
GNU bash, version 5.0.2(1)-release (x86_64-apple-darwin16.7.0)
y no está en el mío.La forma normal, larga y aburrida de definir una función es
Como descubrió este tipo , absolutamente necesita el espacio antes
CODE
y el punto y coma después.Este es un pequeño truco que aprendí de @DigitalTrauma :
Es dos caracteres más cortos y funciona igual de bien, siempre que no necesite trasladar ningún cambio en los valores de las variables después de que regrese la función ( los paréntesis ejecutan el cuerpo en una subshell ).
Como @ jimmy23013 señala en los comentarios, incluso los paréntesis pueden ser innecesarios.
El Manual de referencia de Bash muestra que las funciones se pueden definir de la siguiente manera:
Un comando compuesto puede ser:
until
,while
ofor
if
,case
,((...))
o[[...]]
(...)
o{...}
Eso significa que todo lo siguiente es válido:
Y he estado usando llaves como una ventosa ...
fuente
f()while ...
f()if ...
y otros comandos compuestos.f()CODE
era legal. Resulta quef()echo hi
es legal en pdksh y zsh, pero no en bash.for
predeterminado de posicionales:f()for x do : $x; done;set -x *;PS4=$* f "$@"
o algo así.:
es un comando que no hace nada, su estado de salida siempre tiene éxito, por lo que puede usarse en lugar detrue
.fuente
:(){:|:}
Mas consejos
Abusar del operador ternario,
((test)) && cmd1 || cmd2
o[ test ] && cmd1 || cmd2
, tanto como sea posible.Ejemplos (los recuentos de longitud siempre excluyen la línea superior):
Al usar solo operadores ternarios, esto se puede acortar fácilmente a:
Como nyuszika7h señaló en los comentarios, este ejemplo específico podría acortarse aún más usando
case
:Además, prefiera los paréntesis a las llaves tanto como sea posible. Como los paréntesis son metacaracteres y no una palabra, nunca requieren espacios en ningún contexto. Esto también significa ejecutar tantos comandos en una subshell como sea posible, porque las llaves (es decir,
{
y}
) son palabras reservadas, no metacaracteres, y por lo tanto deben tener espacios en blanco en ambos lados para analizar correctamente, pero los metacaracteres no lo hacen. Supongo que ya sabe que las subcapas no afectan el entorno principal, por lo que suponiendo que todos los comandos de ejemplo se puedan ejecutar de forma segura en una subcapa (lo cual no es típico en ningún caso), puede acortar el código anterior a esto :Además, si no puede hacerlo, usar paréntesis aún puede minimizarlo. Una cosa a tener en cuenta es que solo funciona para enteros, lo que lo hace inútil para los propósitos de este ejemplo (pero es mucho mejor que usar
-eq
para enteros).Una cosa más, evite las comillas siempre que sea posible. Usando ese consejo anterior, puedes minificarlo aún más. Ejemplo:
En condiciones de prueba, prefiera corchetes simples a corchetes dobles tanto como sea posible, con algunas excepciones. Deja caer dos caracteres de forma gratuita, pero en algunos casos no es tan robusto (es una extensión de Bash; vea un ejemplo a continuación). Además, use el argumento de igual simple en lugar del doble. Es un personaje libre para soltar.
Tenga en cuenta esta advertencia, especialmente al verificar la salida nula o una variable indefinida:
En todos los aspectos técnicos, este ejemplo específico sería mejor con
case
...in
:Entonces, la moraleja de esta publicación es esta:
if
/if-else
/ etc. construccionescase
...in
, ya que puede ahorrar bastantes bytes, particularmente en la coincidencia de cadenas.PD: Aquí hay una lista de metacaracteres reconocidos en Bash independientemente del contexto (y puede separar palabras):
EDITAR: Como señaló manatwork, la prueba de paréntesis doble solo funciona para enteros. Además, indirectamente, descubrí que necesitas tener espacios en blanco alrededor del
==
operador. Corregí mi publicación anterior.También era demasiado vago para volver a calcular la longitud de cada segmento, así que simplemente los eliminé. Debería ser fácil encontrar una calculadora de longitud de cadena en línea si es necesario.
fuente
[ $t=="hi" ]
siempre se evaluará a 0, ya que se analiza como[ -n "STRING" ]
.(($t=="hi"))
siempre se evaluará a 0 siempre que $ t tenga un valor no numérico, ya que las cadenas se convierten en enteros en las evaluaciones aritméticas. Algunos casos de prueba: pastebin.com/WefDzWbLcase
sería más corto aquí. Además, no necesita un espacio antes}
, pero sí después{
.=
menos robusto que==
?=
es un mandato de POSIX,==
no lo es.En lugar de
grep -E
,grep -F
,grep -r
, usoegrep
,fgrep
,rgrep
, ahorrando dos caracteres. Los más cortos están en desuso pero funcionan bien.(¡Pidió un consejo por respuesta!)
fuente
Pgrep
paragrep -P
. Aunque veo cómo podría confundirse fácilmente con estopgrep
, que se utiliza para buscar procesos.grep -o
muchoSe puede acceder al elemento 0 de una matriz solo con el nombre de la variable, un ahorro de cinco bytes que especifica explícitamente un índice de 0:
fuente
Si necesita pasar el contenido de una variable a STDIN del siguiente proceso en una tubería, es común hacer eco de la variable en una tubería. Pero puedes lograr lo mismo con una
<<<
cadena bash here :fuente
s=code\ golf
,echo $s|
y<<<$s
(teniendo en cuenta que estos dos últimos trabajos sólo porque hay espacios sin repetidas, etc.).Evita
$( ...command... )
, hay una alternativa que ahorra un personaje y hace lo mismo:fuente
$( )
es necesario si tiene sustituciones de comandos anidadas; de lo contrario, tendría que escapar del interior``
$()
cuando quería ejecutar la sustitución en mi máquina en lugar de lascp
máquina de destino, por ejemplo. En la mayoría de los casos son idénticos.$()
puede hacer si cita las cosas correctamente. (a menos que necesite su comando para sobrevivir a algo que explota$
pero no retrocede). Hay algunas diferencias sutiles en citar cosas dentro de ellos. mywiki.wooledge.org/BashFAQ/082 explica algunas diferencias. A menos que esté jugando al golf, nunca use backticks.echo `bc <<<"\`date +%s\`-12"`
... (¡Es difícil publicar una muestra que contenga una marca de retroceso en el comentario, allí!)Usar
if
para agrupar comandosEn comparación con este consejo que elimina
if
todo, esto solo debería funcionar mejor en algunos casos muy raros, como cuando necesita los valores de retorno deif
.Si tiene un grupo de comandos que termina con un
if
, como estos:En su lugar, puede ajustar los comandos antes
if
en la condición:O si su función termina con un
if
:Puedes quitar las llaves:
fuente
[test] && $if_true || $else
en estas funciones y guardar algunos bytes.&&
y||
Use aritmética
(( ... ))
para condicionesPodrías reemplazar:
por
(Nota: no hay espacio después
if
)o incluso
... o si solo un comando
Además: Ocultar configuración de variable en construcción aritmética:
o igual
... donde si i> 5, entonces c = 1 (no 0;)
fuente
[ ]
lugar de(())
.[ ]
usted necesita el signo de dólar para variable. No veo cómo podría hacer lo mismo con una longitud igual o menor usando[ ]
.((...))
, no es necesaria una nueva línea o espacio. Por ejemplo,for((;10>n++;)){((n%3))&&echo $n;}
pruébelo en línea.Una sintaxis más corta para bucles infinitos (que se puede escapar con
break
oexit
declaraciones) esEsto es más corto que
while true;
ywhile :;
.Si no lo necesita
break
(exit
como la única forma de escapar), puede usar una función recursiva en su lugar.Si necesita un descanso, pero no necesita salir y no necesita trasladar ninguna modificación de variable fuera del ciclo, puede usar una función recursiva con paréntesis alrededor del cuerpo , que ejecuta el cuerpo de la función en una subshell.
fuente
for
Bucles de una líneaSe evaluará una expresión aritmética concatenada con una expansión de rango para cada elemento del rango. Por ejemplo lo siguiente:
evaluará
expression
10 veces.Esto suele ser significativamente más corto que el
for
bucle equivalente :Si no le importa los errores de comando no encontrado, puede eliminar el inicial
:
. Para iteraciones mayores de 10, también puede usar rangos de caracteres, por ejemplo{A..z}
, iterará 58 veces.Como ejemplo práctico, los siguientes dos producen los primeros 50 números triangulares, cada uno en su propia línea:
fuente
for((;0<i--;)){ f;}
Recorrer argumentos
Como se señaló en el bucle Bash "for" sin una parte "in foo bar ..." , el
in "$@;"
infor x in "$@;"
es redundante.De
help for
:Por ejemplo, si queremos cuadrar todos los números dados argumentos posicionales a un script Bash o una función, podemos hacer esto.
Pruébalo en línea!
fuente
Alternativa al gato
Digamos que está intentando leer un archivo y usarlo en otra cosa. Lo que puedes hacer es:
Si el contenido de
bar
wasfoobar
, esto se imprimiríafoo foobar
.Sin embargo, existe una alternativa si está utilizando este método, que ahorra 3 bytes:
fuente
<bar
por la cual por sí solo no funciona, pero si lo coloca en backticks sí?<
pone un archivo a un comando, pero en este caso se pone en la salida estándar, debido a una peculiaridad. Los backticks evalúan esto juntos.`cat`
?Usar en
[
lugar de[[
ytest
cuando sea posibleEjemplo:
Usar en
=
lugar de==
para compararEjemplo:
Tenga en cuenta que debe tener espacios alrededor del signo igual o de lo contrario no funcionará. Lo mismo aplica
==
basado en mis pruebas.fuente
[
vs.[[
puede depender de la cantidad de cotizaciones requeridas: pastebin.com/UPAGWbDQ[
...]
==/bin/test
, pero[[
...]]
! =/bin/test
Y uno nunca debería preferir[
...]
a[[
...]]
fuera de codegolfAlternativas a
head
line
es tres bytes más corto quehead -1
, pero está en desuso .sed q
es dos bytes más corto quehead -1
.sed 9q
es un byte más corto quehead -9
.fuente
line
de util-linux paquete para leer una sola línea.tr -cd
es más corto quegrep -o
Por ejemplo, si necesita contar espacios,
grep -o <char>
(imprimir solo el coincidente) da 10 bytes mientras quetr -cd <char>
(eliminar complemento de<char>
) da 9.( fuente )
Tenga en cuenta que ambos dan resultados ligeramente diferentes.
grep -o
devuelve resultados separados por líneas mientras lostr -cd
da a todos en la misma línea, por lo quetr
puede no ser siempre favorable.fuente
Acortar nombres de archivo
En un desafío reciente, estaba tratando de leer el archivo
/sys/class/power_supply/BAT1/capacity
, sin embargo, esto se puede acortar/*/*/*/*/capac*y
ya que no existe ningún otro archivo con ese formato.Por ejemplo, si tenía un directorio que
foo/
contenía los archivosfoo, bar, foobar, barfoo
y deseaba hacer referencia al archivofoo/barfoo
, puede usarlofoo/barf*
para guardar un byte.El
*
representa "cualquier cosa", y es equivalente a la expresión regular.*
.fuente
Use una tubería para el
:
comando en lugar de/dev/null
. El:
incorporado comerá toda su entrada.fuente
echo a|tee /dev/stderr|:
No imprimirá nada.echo a|tee /dev/stderr|:
imprimí un en mi computadora, pero en otro lugar SIGPIPE podría matar el tee primero. Puede depender de la versión detee
.tee >(:) < <(seq 1 10)
funcionará, perotee /dev/stderr | :
no lo hará. Inclusoa() { :;};tee /dev/stderr < <(seq 1 10)| a
no imprima nada.:
intrínseco no se come en absoluto ... si supones una entrada a los dos puntos, podrías inundar una tubería en un error ... pero puedes flotar una redirección por dos puntos, o dejar caer un proceso con él ...:| while i>&$(($??!$?:${#?})) command shit; do [ -s testitsoutput ]; done
o, sin embargo, esa pseudo sugerencia se aplica ... también, ¿eres consciente de que eres tan fantasmal como yo? ... evitar a toda costa el< <(psycho shit i can alias to crazy math eat your world; okay? anyway, ksh93 has a separate but equal composite char placement)
split
tiene otra sintaxis (en desuso, pero a nadie le importa) para dividir la entrada en secciones deN
líneas cada una: en lugar desplit -lN
que pueda usar,split -N
por ejemplosplit -9
.fuente
Expande las pruebas
Esencialmente, el shell es un tipo de lenguaje macro, o al menos un híbrido o algún tipo. Cada línea de comando se puede dividir básicamente en dos partes: la parte de análisis / entrada y la parte de expansión / salida.
La primera parte es en lo que la mayoría de la gente se enfoca porque es la más simple: ves lo que obtienes. La segunda parte es lo que muchos evitan incluso tratar de entender muy bien y es por qué la gente dice que las cosas
eval
son malas y siempre citan sus expansiones : la gente quiere que el resultado de la primera parte sea igual a la primera. Eso está bien, pero conduce a ramificaciones de código innecesariamente largas y toneladas de pruebas extrañas.Las expansiones son de autoevaluación . Los
${param[[:]#%+-=?]word}
formularios son más que suficientes para validar el contenido de un parámetro, son anidables y se basan en la evaluación de NUL , que es lo que la mayoría de la gente espera de las pruebas de todos modos.+
puede ser especialmente útil en bucles:... mientras
read
las líneas no en blanco se"${r:+set}"
expanden"set"
y las posiciones se$r
agregan. Pero cuando una línea en blanco esread
,$r
está vacía y se"${r:+set}"
expande a""
, que es un comando no válido. Pero debido a que la línea de comandos se expande antes de la""
se busca comando nulo,"${r:=$*}"
toma los valores de todos los posicionales concatenados en el primer byte$IFS
también.r()
podría llamarse nuevamente en un|{
comando compuesto;}
con un valor diferente para$IFS
obtener el siguiente párrafo de entrada, ya que es ilegal que un shell se almaceneread
más allá de la siguiente\n
línea de entrada.fuente
Use la recursión de la cola para acortar los bucles:
Estos son equivalentes en comportamiento (aunque probablemente no en uso de memoria / PID):
Y estos son más o menos equivalentes:
(técnicamente los últimos tres siempre ejecutarán el cuerpo al menos una vez)
El uso
$0
requiere que su script esté en un archivo, no pegado en el indicador bash.Finalmente, su pila puede desbordarse, pero guarda algunos bytes.
fuente
A veces es más corto usar el valor
expr
incorporado para mostrar el resultado de una expresión aritmética simple en lugar de la habitualecho $[ ]
. Por ejemplo:es un byte más corto que:
fuente
Use en
pwd
lugar deecho
generar una línea de salida¿Necesita poner una línea en stdout pero no le importan los contenidos y desea restringir su respuesta a los componentes integrados de shell?
pwd
es un byte más corto queecho
.fuente
Las citas pueden omitirse al imprimir cadenas.
Salida en SM-T335 LTE, Android 5.1.1:
fuente
Al asignar elementos de matriz no continuos, aún puede omitir los índices sucesivos de fragmentos continuos:
El resultado es el mismo:
De acuerdo a
man bash
:fuente
Imprime la primera palabra en una cadena
Si la cadena está en la variable
a
y no contiene caracteres de escape y formato (\
y%
), use esto:Pero sería más largo que el siguiente código si fuera necesario para guardar el resultado en una variable en lugar de imprimirlo:
fuente
Hacer 2 bucles de inserción con 1
for
instrucción:fuente
Asignar e imprimir cadenas entre comillas
Si desea asignar una cadena entre comillas a una variable y luego imprimir el valor de esa variable, la forma habitual de hacerlo sería:
Si no
a
se configuró anteriormente, esto se puede acortar a:Si
a
se configuró previamente, entonces debería usarse en su lugar:Tenga en cuenta que esto solo es útil si la cadena requiere comillas (por ejemplo, contiene espacios en blanco). Sin comillas,
a=123;echo $a
es igual de breve.fuente
${a+foo}
no fijaa
.