Que dicas gerais você tem para jogar golfe no Bash? Estou procurando idéias que possam ser aplicadas para codificar problemas de golfe em geral que sejam pelo menos um pouco específicos para o Bash (por exemplo, "remover comentários" não é uma resposta). Poste uma dica por resposta.
55
sh
e qual shell permite essafor
sintaxe? é expressamente permitido entrarzsh
.sh
tempos antigos e que o Bash também o permite por causa disso, embora, infelizmente, não tenha uma citação.csh
, provavelmente - foi assim que eles trabalharam nessa concha.ksh93
coisa acima poderia ser;{1..10}
bash
printf %s\\n {1..10}
for((;i++<10)){ echo $i;}
é mais curto quefor i in {1..10};{ echo $i;}
Para expansão aritmética, use em
$[…]
vez de$((…))
:Em expansões aritméticas, não use
$
:A expansão aritmética é realizada em subscritos de matriz indexada, portanto, não use
$
nem lá:Em expansões aritméticas, não use
${…}
:fonte
while((i--))
, que funciona, comwhile[i--]
ouwhile $[i--]
não funcionou para mim. GNU bash, versão 4.3.46 (1)y=`bc<<<"($x*2.2)%10/1"`
... exemplo de usobc
para cálculos não inteiros ... observe que/1
no final trunca o decimal resultante para um int.s=$((i%2>0?s+x:s+y))
... exemplo de uso do operador ternário na aritmética do bash. É mais curto queif..then..else
ou[ ] && ||
GNU bash, version 5.0.2(1)-release (x86_64-apple-darwin16.7.0)
e não está no meu.A maneira normal, longa e chata de definir uma função é
Como esse cara descobriu, você precisa absolutamente do espaço antes
CODE
e do ponto e vírgula depois dele.Este é um pequeno truque que aprendi no @DigitalTrauma :
São dois caracteres mais curtos e funciona da mesma forma, desde que você não precise transportar nenhuma alteração nos valores das variáveis após o retorno da função ( os parênteses executam o corpo em uma subshell ).
Como @ jimmy23013 aponta nos comentários, até os parênteses podem ser desnecessários.
O Manual de Referência do Bash mostra que as funções podem ser definidas da seguinte maneira:
Um comando composto pode ser:
until
,while
oufor
if
,case
,((...))
ou[[...]]
(...)
ou{...}
Isso significa que todos os itens a seguir são válidos:
E eu tenho usado colchetes como um otário ...
fonte
f()while ...
f()if ...
e outros comandos compostos.f()CODE
era legal. Acontece que issof()echo hi
é legal no pdksh e no zsh, mas não no bash.for
o padrão de posicionais:f()for x do : $x; done;set -x *;PS4=$* f "$@"
ou algo assim.:
é um comando que não faz nada, seu status de saída sempre é bem-sucedido e, portanto, pode ser usado em vez detrue
.fonte
:(){:|:}
Mais dicas
Abuse o operador ternário
((test)) && cmd1 || cmd2
ou[ test ] && cmd1 || cmd2
, tanto quanto possível.Exemplos (contagens de comprimento sempre excluem a linha superior):
Usando apenas operadores ternários, isso pode ser facilmente reduzido para:
Como nyuszika7h apontou nos comentários, este exemplo específico pode ser reduzido ainda mais usando
case
:Além disso, prefira parênteses a chaves, tanto quanto possível. Como os parênteses são um metacaractere, e não uma palavra, eles nunca requerem espaços em nenhum contexto. Isso também significa executar o maior número possível de comandos em um subshell, porque chaves entre chaves (ie
{
e}
) são palavras reservadas, não meta caracteres, e, portanto, precisam ter espaço em branco em ambos os lados para analisar corretamente, mas as meta caracteres não. Suponho que você já saiba que os subshells não afetam o ambiente pai; portanto, supondo que todos os comandos de exemplo possam ser executados com segurança em um subshell (o que não é típico em nenhum caso), você pode encurtar o código acima para isso :Além disso, se você não puder, usar parênteses ainda poderá reduzi-lo um pouco. Um aspecto a ter em mente é que ele funciona apenas para números inteiros, o que o torna inútil para os fins deste exemplo (mas é muito melhor do que usar
-eq
para números inteiros).Mais uma coisa, evite aspas sempre que possível. Usando o conselho acima, você pode reduzi-lo ainda mais. Exemplo:
Nas condições de teste, prefira colchetes simples a duplos, tanto quanto possível, com algumas exceções. Ele solta dois caracteres de graça, mas em alguns casos não é tão robusto (é uma extensão do Bash - veja abaixo um exemplo). Além disso, use o argumento igual a um e não o dobro. É um personagem grátis para soltar.
Observe esta ressalva, especialmente na verificação de saída nula ou de uma variável indefinida:
Com toda a tecnicidade, este exemplo específico seria melhor com
case
...in
:Então, a moral deste post é a seguinte:
if
/if-else
/ etc. construções.case
...in
, pois ele pode economizar alguns bytes, principalmente na correspondência de cadeias.PS: Aqui está uma lista de meta-caracteres reconhecidos no Bash, independentemente do contexto (e podem separar palavras):
EDIT: Como apontou manatwork, o teste de parênteses duplos funciona apenas para números inteiros. Além disso, indiretamente, descobri que você precisa ter um espaço em branco ao redor do
==
operador. Corrigido minha postagem acima.Eu também estava com preguiça de recalcular o comprimento de cada segmento, então simplesmente os removi. Deve ser fácil encontrar uma calculadora de comprimento de cordas online, se necessário.
fonte
[ $t=="hi" ]
sempre avaliará como 0, pois é analisado como[ -n "STRING" ]
.(($t=="hi"))
sempre avaliará como 0, desde que $ t tenha valor não numérico, pois as strings são forçadas a números inteiros nas avaliações aritméticas. Alguns casos de teste: pastebin.com/WefDzWbLcase
seria mais curto aqui. Além disso, você não precisa de um espaço antes}
, mas precisa depois{
.=
seria menos robusto do que==
?=
é mandatado pelo POSIX,==
não é.Em vez de
grep -E
,grep -F
,grep -r
, usoegrep
,fgrep
,rgrep
, salvando dois caracteres. Os mais curtos estão obsoletos, mas funcionam bem.(Você pediu uma dica por resposta!)
fonte
Pgrep
paragrep -P
. Embora eu veja como isso pode ser facilmente confundidopgrep
, o qual é usado para procurar processos.grep -o
muitoO elemento 0 de uma matriz pode ser acessado apenas com o nome da variável, economizando cinco bytes e especificando explicitamente um índice 0:
fonte
Se você precisar passar o conteúdo de uma variável para STDIN do próximo processo em um pipeline, é comum ecoar a variável em um pipeline. Mas você pode conseguir o mesmo com uma
<<<
string bash here :fonte
s=code\ golf
,echo $s|
e<<<$s
(tendo em mente que os dois últimos trabalhos só porque existem espaços não repetidas, etc.).Evite
$( ...command... )
, existe uma alternativa que salva um caractere e faz a mesma coisa:fonte
$( )
é necessário se você tiver aninhado substituições de comando; caso contrário, você teria que escapar do interior``
$()
quando eu queria executar a substituição na minha máquina em vez da máquina descp
destino, por exemplo. Na maioria dos casos, são idênticos.$()
pode fazer se você citar as coisas corretamente. (a menos que você precise do seu comando para sobreviver a algo que munges,$
mas não backticks). Existem algumas diferenças sutis em citar coisas dentro deles. mywiki.wooledge.org/BashFAQ/082 explica algumas diferenças. A menos que você esteja jogando golfe, nunca use backticks.echo `bc <<<"\`date +%s\`-12"`
... (É difícil amostra pós contendo backtick no comentário, não;!)Use
if
para agrupar comandosComparado a essa dica que remove
if
tudo, isso só deve funcionar melhor em alguns casos muito raros, como quando você precisa dos valores de retorno deif
.Se você possui um grupo de comandos que termina com a
if
, como estes:Você pode agrupar os comandos antes
if
na condição:Ou se sua função terminar com um
if
:Você pode remover os aparelhos:
fonte
[test] && $if_true || $else
nessas funções e salvar alguns bytes.&&
e||
Use aritmética
(( ... ))
para condiçõesVocê pode substituir:
por
(Nota: não há espaço depois
if
)ou mesmo
... ou se apenas um comando
Além disso: oculte a configuração variável na construção aritmética:
ou mesmo
... onde i> 5, então c = 1 (não 0;)
fonte
[ ]
vez de(())
.[ ]
você precisa do cifrão para variável. Não vejo como você poderia fazer o mesmo com o mesmo ou menor comprimento usando[ ]
.((...))
, nenhuma nova linha ou espaço será necessário. Por exemplo,for((;10>n++;)){((n%3))&&echo $n;}
experimente online!Uma sintaxe mais curta para loops infinitos (que podem ser escapados com
break
ouexit
instruções) éIsso é mais curto que
while true;
ewhile :;
.Se você não precisar
break
(comexit
a única maneira de escapar), poderá usar uma função recursiva.Se você precisar interromper, mas não precisar sair e não precisar realizar nenhuma modificação de variável fora do loop, poderá usar uma função recursiva com parênteses ao redor do corpo , que executam o corpo da função em um subshell.
fonte
for
Loops de uma linhaUma expressão aritmética concatenada com uma expansão de intervalo será avaliada para cada item no intervalo. Por exemplo, o seguinte:
avaliará
expression
10 vezes.Isso geralmente é significativamente menor que o
for
loop equivalente :Se você não se importa com o comando não encontrou erros, pode remover o inicial
:
. Para iterações maiores que 10, você também pode usar intervalos de caracteres, por exemplo{A..z}
, iterará 58 vezes.Como exemplo prático, os seguintes itens produzem os primeiros 50 números triangulares, cada um em sua própria linha:
fonte
for((;0<i--;)){ f;}
Argumentos sobre loop
Como observado no Bash laço “for” sem “em foo bar ...” parte , o
in "$@;"
emfor x in "$@;"
é redundante.De
help for
:Por exemplo, se quisermos colocar todos os números ao quadrado, com argumentos posicionais para um script ou função do Bash, podemos fazer isso.
Experimente online!
fonte
Alternativa para gato
Digamos que você esteja tentando ler um arquivo e usá-lo em outra coisa. O que você pode fazer é:
Se o conteúdo de
bar
fossefoobar
, isso seria impressofoo foobar
.No entanto, existe uma alternativa se você estiver usando esse método, que economiza 3 bytes:
fonte
<bar
pela qual, por si só, não funciona, mas colocá-lo em backticks funciona?<
coloca um arquivo em um comando, mas, neste caso, o coloca na saída padrão devido a uma peculiaridade. Os backticks avaliam isso juntos.`cat`
?Use em
[
vez de[[
etest
quando possívelExemplo:
Use em
=
vez de==
para comparaçãoExemplo:
Observe que você deve ter espaços ao redor do sinal de igual, caso contrário não funcionará. O mesmo se aplica a
==
baseado nos meus testes.fonte
[
vs[[
pode depender da quantidade de citações necessários: pastebin.com/UPAGWbDQ[
...]
==/bin/test
, mas[[
...]]
! =/bin/test
E nunca se deve preferir[
...]
sobre[[
...]]
fora do codegolfAlternativas para
head
line
é três bytes menor quehead -1
, mas está sendo preterido .sed q
é dois bytes menor quehead -1
.sed 9q
é um byte menor quehead -9
.fonte
line
do pacote util-linux para ler uma única linha.tr -cd
é mais curto quegrep -o
Por exemplo, se você precisar contar espaços,
grep -o <char>
(imprima apenas o correspondente) fornece 10 bytes enquantotr -cd <char>
(excluir complemento de<char>
) fornece 9.( fonte )
Observe que ambos fornecem resultados ligeiramente diferentes.
grep -o
retorna resultados separados por linha etr -cd
fornece todos na mesma linha; portanto,tr
nem sempre é favorável.fonte
Encurtar nomes de arquivo
Em um desafio recente, eu estava tentando ler o arquivo
/sys/class/power_supply/BAT1/capacity
, no entanto, isso pode ser reduzido,/*/*/*/*/capac*y
pois não existe outro arquivo com esse formato.Por exemplo, se você tivesse um diretório
foo/
contendo os arquivosfoo, bar, foobar, barfoo
e desejasse fazer referência ao arquivofoo/barfoo
, poderá usarfoo/barf*
para salvar um byte.O
*
representa "qualquer coisa" e é equivalente ao regex.*
.fonte
Use um pipe para o
:
comando em vez de/dev/null
. O:
built-in vai comer toda a sua entrada.fonte
echo a|tee /dev/stderr|:
não imprimirá nada.echo a|tee /dev/stderr|:
imprimi uma no meu computador, mas em outros lugares o SIGPIPE pode matar o tee primeiro. Pode depender da versão dotee
.tee >(:) < <(seq 1 10)
funcionará, mastee /dev/stderr | :
não funcionará. Mesmoa() { :;};tee /dev/stderr < <(seq 1 10)| a
não imprima nada.:
intrínseco não come nada ... se você supor entrada para dois pontos, poderá inundar um cano para erro de saída ... mas pode flutuar um redirecionamento por dois pontos, ou abandonar um processo com ele ...:| while i>&$(($??!$?:${#?})) command shit; do [ -s testitsoutput ]; done
ou no entanto essa pseudo sugestão se aplica ... também, você sabe que é quase tão fantasmagórico quanto eu? ... evite a todo custo o< <(psycho shit i can alias to crazy math eat your world; okay? anyway, ksh93 has a separate but equal composite char placement)
split
tem outra sintaxe (obsoleta, mas ninguém liga) para dividir a entrada em seções deN
linhas cada: em vez desplit -lN
você pode usar,split -N
por exemplosplit -9
.fonte
Expanda os testes
Essencialmente, o shell é um tipo de linguagem macro, ou pelo menos um híbrido ou algum tipo. Cada linha de comando pode ser basicamente dividida em duas partes: a parte de análise / entrada e a parte de expansão / saída.
A primeira parte é o foco da maioria das pessoas, porque é a mais simples: você vê o que recebe. A segunda parte é o que muitos evitam tentar entender muito bem, e é por isso que as pessoas dizem coisas
eval
ruins e sempre citam suas expansões - as pessoas querem que o resultado da primeira parte seja igual ao primeiro. Tudo bem - mas isso leva a ramificações de código desnecessariamente longas e a toneladas de testes estranhos.As expansões são autotestes . Os
${param[[:]#%+-=?]word}
formulários são mais que suficientes para validar o conteúdo de um parâmetro, são aninhados e baseiam-se na avaliação do NUL - que é o que a maioria das pessoas espera dos testes de qualquer maneira.+
pode ser especialmente útil em loops:... enquanto
read
puxa linhas não em branco se"${r:+set}"
expande"set"
e as posições são$r
anexadas. Mas quando uma linha em branco éread
,$r
está vazia e se"${r:+set}"
expande para""
- que é um comando inválido. Porém, como a linha de comando é expandida antes da""
pesquisa do comando nulo,"${r:=$*}"
leva também os valores de todas as posições concatenadas no primeiro byte$IFS
.r()
poderia ser chamado novamente no|{
comando composto;}
com um valor diferente para$IFS
obter o próximo parágrafo de entrada também, pois é ilegal que um shell sejaread
armazenado em buffer além da próxima linha de\n
ew na entrada.fonte
Use a recursão da cauda para diminuir os loops:
Eles são equivalentes em comportamento (embora provavelmente não estejam no uso de memória / PID):
E estes são aproximadamente equivalentes:
(tecnicamente, os três últimos sempre executam o corpo pelo menos uma vez)
O uso
$0
requer que seu script esteja em um arquivo, não colado no prompt do bash.Eventualmente, sua pilha pode estourar, mas você salva alguns bytes.
fonte
Às vezes, é mais curto usar o
expr
built-in para exibir o resultado de uma expressão aritmética simples, em vez da usualecho $[ ]
. Por exemplo:é um byte menor que:
fonte
Use em
pwd
vez deecho
para gerar uma linha de saídaPrecisa colocar uma linha no stdout, mas não se importa com o conteúdo e deseja restringir sua resposta aos shell builtins?
pwd
é um byte menor queecho
.fonte
As cotações podem ser omitidas ao imprimir seqüências de caracteres.
Saída no SM-T335 LTE, Android 5.1.1:
fonte
Ao atribuir itens de matriz não contínuos, você ainda pode pular os índices sucessivos de blocos contínuos:
O resultado é o mesmo:
De acordo com
man bash
:fonte
Imprimir a primeira palavra em uma string
Se a sequência estiver na variável
a
e não contiver caracteres de escape e formato (\
e%
), use este:Mas seria maior que o código a seguir se for necessário salvar o resultado em uma variável em vez de imprimir:
fonte
Fazendo 2 loop de incorporação com 1
for
instrução:fonte
Atribuir e imprimir strings entre aspas
Se você deseja atribuir uma string entre aspas a uma variável e depois imprimir o valor dessa variável, a maneira usual de fazer isso seria:
Se
a
anteriormente não estava definido, isso pode ser reduzido para:Se
a
foi definido anteriormente, isso deve ser usado:Observe que isso só é útil se a sequência exigir aspas (por exemplo, contém espaço em branco). Sem aspas,
a=123;echo $a
é tão curto.fonte
${a+foo}
não definea
.