Como usar comandos shell no Makefile

99

Estou tentando usar o resultado de lsem outros comandos (por exemplo, echo, rsync):

all:
    <Building, creating some .tgz files - removed for clarity>
    FILES = $(shell ls)
    echo $(FILES)

Mas eu consigo:

make
FILES = Makefile file1.tgz file2.tgz file3.tgz
make: FILES: No such file or directory
make: *** [all] Error 1

Eu tentei usar echo $$FILES, echo ${FILES}e echo $(FILES), sem sorte.

Adam Matan
fonte

Respostas:

149

Com:

FILES = $(shell ls)

recuado embaixo allassim, é um comando de construção. Portanto, isso se expande e $(shell ls), em seguida, tenta executar o comando FILES ....

Se FILESé suposto ser uma makevariável, essas variáveis ​​precisam ser atribuídas fora da parte da receita, por exemplo:

FILES = $(shell ls)
all:
        echo $(FILES)

Claro, isso significa que FILESserá definido como "saída de ls" antes de executar qualquer um dos comandos que criam os arquivos .tgz. (Embora, como Kaz observa, a variável seja expandida novamente a cada vez, então eventualmente ela incluirá os arquivos .tgz; algumas variantes de criação devem FILES := ...evitar isso, para eficiência e / ou correção. 1 )

Se FILESdeveria ser uma variável shell, você pode defini-la, mas precisa fazer isso em shell-ese, sem espaços e entre aspas:

all:
        FILES="$(shell ls)"

No entanto, cada linha é executada por um shell separado, então essa variável não sobreviverá à próxima linha, então você deve usá-la imediatamente:

        FILES="$(shell ls)"; echo $$FILES

Isso tudo é um pouco bobo, pois o shell se expandirá *(e outras expressões do shell glob) para você em primeiro lugar, então você pode apenas:

        echo *

como seu comando shell.

Finalmente, como regra geral (não se aplica realmente a este exemplo): como as notas do esperanto nos comentários, usar a saída de lsnão é totalmente confiável (alguns detalhes dependem dos nomes dos arquivos e às vezes até da versão de ls; algumas versões de lstentativa de sanitizar a saída em alguns casos). Assim, como 10b0 e idelic note, se você estiver usando GNU make você pode usar $(wildcard)e $(subst ...)realizar tudo dentro de makesi (evitando quaisquer problemas de "caracteres estranhos no nome do arquivo"). (Em shscripts, incluindo a parte da receita de makefiles, outro método é usar find ... -print0 | xargs -0para evitar tropeçar em espaços em branco, novas linhas, caracteres de controle e assim por diante.)


1 O GNU Make documenta ainda que POSIX fez ::=atribuições adicionais em 2012 . Não encontrei um link de referência rápida para um documento POSIX para isso, nem sei de antemão quais makevariantes suportam ::=atribuição, embora o GNU faça hoje, com o mesmo significado que :=, ou seja, fazer a atribuição agora com expansão.

Observe que VAR := $(shell command args...)também pode ser escrito VAR != command args...em várias makevariantes, incluindo todas as variantes modernas do GNU e do BSD, até onde eu sei. Essas outras variantes não têm, $(shell)portanto, o uso VAR != command args...é superior por ser mais curto e funcionar em mais variantes.

Torek
fonte
Obrigado. Quero usar um comando elaborado ( lscom sede cortar, por exemplo) e depois usar os resultados em rsync e outros comandos. Eu tenho que repetir o comando longo indefinidamente? Não posso armazenar os resultados em uma variável interna Make?
Adam Matan,
1
Gnu make pode ter uma maneira de fazer isso, mas eu nunca usei, e todos os makefiles terrivelmente complicados que usamos usam apenas variáveis ​​de shell e comandos shell gigantes de uma linha construídos com "; \" no final de cada linha como necessário. (não é possível fazer com que a codificação do código funcione com a sequência da barra invertida aqui, hmm)
torek de
1
Talvez algo como: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris,
2
@William: makepode fazer isso sem usar o shell: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic
1
Gostaria de salientar que, embora neste caso não pareça muito importante, você não deve analisar automaticamente a saída de ls. O objetivo do ls é mostrar informações aos seres humanos, não para serem encadeadas em scripts. Mais informações aqui: mywiki.wooledge.org/ParsingLs Provavelmente, no caso de 'make' não oferecer a expansão de curinga apropriada, "find" é melhor do que "ls".
Raúl Salinas-Monteagudo
53

Além disso, além da resposta de torek: uma coisa que se destaca é que você está usando uma atribuição de macro avaliada vagarosamente.

Se você estiver no GNU Make, use a :=atribuição em vez de =. Esta atribuição faz com que o lado direito seja expandido imediatamente e armazenado na variável do lado esquerdo.

FILES := $(shell ...)  # expand now; FILES is now the result of $(shell ...)

FILES = $(shell ...)   # expand later: FILES holds the syntax $(shell ...)

Se você usar a =atribuição, significa que cada ocorrência de $(FILES)estará expandindo a $(shell ...)sintaxe e, portanto, invocando o comando shell. Isso fará com que seu trabalho seja mais lento ou até mesmo terá algumas consequências surpreendentes.

Kaz
fonte
1
Agora que temos a lista, como iteramos cada item da lista e executamos um comando nele? Como construir ou testar?
anon58192932
3
@ anon58192932 Isso iteração específica, para executar um comando, é feito geralmente em um fragmento de sintaxe shell em uma receita de construção, de modo que ocorre no shell, em vez de fazer: for x in $(FILES); do command $$x; done. Observe o dobrado $$que passa um único $para o shell. Além disso, os fragmentos de casca são de uma linha; para escrever código de shell de várias linhas, você usa a continuação de barra invertida que é processada por makesi mesma e dobrada em uma linha. O que significa que os pontos-e-vírgulas separadores de comandos do shell são obrigatórios.
Kaz