Comandos bash multilinha em makefile

120

Tenho uma maneira muito confortável de compilar meu projeto por meio de algumas linhas de comandos bash. Mas agora preciso compilá-lo via makefile. Considerando que cada comando é executado em seu próprio shell, minha dúvida é qual a melhor maneira de executar um comando bash multi-linha, dependente um do outro, no makefile? Por exemplo, assim:

for i in `find`
do
    all="$all $i"
done
gcc $all

Além disso, alguém pode explicar por que mesmo o comando de linha única bash -c 'a=3; echo $a > file'funciona corretamente no terminal, mas cria um arquivo vazio no caso de makefile?

Jofsey
fonte
4
A segunda parte deve ser uma pergunta separada. Adicionar outra pergunta apenas cria ruído.
10b0

Respostas:

136

Você pode usar barra invertida para a continuação da linha. No entanto, observe que o shell recebe todo o comando concatenado em uma única linha, portanto, você também precisa terminar algumas das linhas com um ponto e vírgula:

foo:
    for i in `find`;     \
    do                   \
        all="$$all $$i"; \
    done;                \
    gcc $$all

Mas se você deseja apenas pegar toda a lista retornada pela findinvocação e passá-la para gcc, você não precisa necessariamente de um comando multilinha:

foo:
    gcc `find`

Ou, usando uma $(command)abordagem mais convencional de shell (observe o $escape):

foo:
    gcc $$(find)
Eldar Abusalimov
fonte
2
Para multilinhas, você ainda precisa escapar dos cifrões, conforme indicado em comentários em outros lugares.
tripleee
1
Sim, uma resposta curta 1. $: = $$ 2. anexar multiline com \ BTW bash caras geralmente recomendam substituir chamada `find` por $$ (find)
Yauhen Yakimovich
89

Conforme indicado na pergunta, cada subcomando é executado em seu próprio shell . Isso torna a escrita de scripts de shell não triviais um pouco confusa - mas é possível! A solução é consolidar seu script no que o make considerará um único subcomando (uma única linha).

Dicas para escrever scripts de shell em makefiles:

  1. Fuja do uso do script $substituindo por$$
  2. Converta o script para funcionar como uma única linha inserindo ;entre os comandos
  3. Se você quiser escrever o script em várias linhas, escape do fim de linha com \
  4. Opcionalmente, comece com set -epara corresponder à provisão do make para abortar em caso de falha de subcomando
  5. Isso é totalmente opcional, mas você pode colocar o script entre colchetes ()ou {}para enfatizar a coesão de uma sequência de múltiplas linhas - que esta não é uma sequência de comando makefile típica

Aqui está um exemplo inspirado no OP:

mytarget:
    { \
    set -e ;\
    msg="header:" ;\
    for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
    msg="$$msg :footer" ;\
    echo msg=$$msg ;\
    }
Brent Bradburn
fonte
4
Você também pode desejar SHELL := /bin/bashem seu makefile habilitar recursos específicos do BASH, como substituição de processo .
Brent Bradburn
8
Ponto sutil: O espaço depois {é crucial para evitar a interpretação de {setum comando desconhecido.
Brent Bradburn
3
Ponto sutil nº 2: No makefile provavelmente não importa, mas a distinção entre empacotar com {}e ()faz uma grande diferença se você às vezes deseja copiar o script e executá-lo diretamente de um prompt de shell. Você pode causar estragos em sua instância do shell declarando variáveis ​​e, especialmente, modificando o estado com set, dentro de {}. ()evita que o script modifique seu ambiente, o que provavelmente é o preferido. Exemplo ( isso vai acabar a sua sessão de shell ): { set -e ; } ; false.
Brent Bradburn de
Você pode incluir comentários usando o seguinte formulário command ; ## my comment \` (the comment is between :; `e` \ `). Isso parece funcionar bem, exceto que se você executar o comando manualmente (copiando e colando), o histórico do comando incluirá o comentário de uma forma que quebra o comando (se você tentar reutilizá-lo). [Observação: o realce da sintaxe foi quebrado para este comentário devido ao uso de barra invertida dentro da crase.]
Brent Bradburn
Se você quiser quebrar uma string em bash / perl / script dentro do makefile, feche a aspas da string, barra invertida, nova linha (sem recuo) aspas abertas da string. Exemplo; perl -e QUOTE print 1; CITAÇÕES BACKSLASH NEWLINE CITAÇÕES imprimir 2 CITAÇÕES
mosh
2

O que há de errado em apenas invocar os comandos?

foo:
       echo line1
       echo line2
       ....

E para sua segunda pergunta, você precisa escapar $de usando $$, em vez disso, ie bash -c '... echo $$a ...'.

EDITAR: Seu exemplo poderia ser reescrito em um script de linha única como este:

gcc $(for i in `find`; do echo $i; done)
JesperE
fonte
5
Porque "cada comando é executado em seu próprio shell", enquanto eu preciso usar o resultado do comando1 no comando2. Como no exemplo.
Jofsey,
Se você precisa propagar informações entre as invocações do shell, você precisa usar alguma forma de armazenamento externo, como um arquivo temporário. Mas você sempre pode reescrever seu código para executar vários comandos no mesmo shell.
JesperE
+1, especialmente para a versão simplificada. E não é o mesmo que fazer apenas `` gcc `find```?
Eldar Abusalimov
1
Sim, ele é. Mas você pode fazer coisas mais complexas do que apenas 'ecoar'.
JesperE
2

Obviamente, a maneira correta de escrever um Makefile é documentar quais destinos dependem de quais fontes. No caso trivial, a solução proposta fará foodepender de si mesma, mas makeé claro, é inteligente o suficiente para descartar uma dependência circular. Mas se você adicionar um arquivo temporário ao seu diretório, ele se tornará "magicamente" parte da cadeia de dependências. Melhor criar uma lista explícita de dependências de uma vez por todas, talvez por meio de um script.

GNU make sabe como executar gccpara produzir um executável a partir de um conjunto de arquivos .ce .h, então talvez tudo o que você realmente precisa seja

foo: $(wildcard *.h) $(wildcard *.c)
triplo
fonte
1

A diretiva ONESHELL permite escrever várias receitas de linha para serem executadas na mesma invocação do shell.

SOURCE_FILES = $(shell find . -name '*.c')

.ONESHELL:
foo: ${SOURCE_FILES}
    for F in $^; do
        gcc -c $$F
    done

Porém, há uma desvantagem: caracteres de prefixo especiais ('@', '-' e '+') são interpretados de forma diferente.

https://www.gnu.org/software/make/manual/html_node/One-Shell.html

Jean-Baptiste Poittevin
fonte