Cara menggunakan perintah shell di Makefile

99

Saya mencoba menggunakan hasil lsdi perintah lain (misalnya echo, rsync):

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

Tapi saya mendapatkan:

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

Saya sudah mencoba menggunakan echo $$FILES, echo ${FILES}dan echo $(FILES), tidak berhasil.

Adam Matan
sumber

Jawaban:

149

Dengan:

FILES = $(shell ls)

menjorok di bawahnya allseperti itu, itu adalah perintah build. Jadi ini mengembang $(shell ls), lalu mencoba menjalankan perintahFILES ... .

Jika FILESseharusnya menjadi makevariabel, variabel ini perlu ditempatkan di luar bagian resep, misalnya:

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

Tentu saja, itu berarti FILESakan disetel ke "keluaran dari ls" sebelum menjalankan perintah apa pun yang membuat berkas .tgz. (Meskipun seperti yang dicatat Kaz , variabel tersebut diperluas kembali setiap kali, jadi pada akhirnya ia akan menyertakan file .tgz; beberapa varian make harus FILES := ...menghindari ini, demi efisiensi dan / atau kebenaran. 1 )

Jika FILESseharusnya menjadi variabel shell, Anda dapat mengaturnya tetapi Anda perlu melakukannya dalam shell-ese, tanpa spasi, dan dikutip:

all:
        FILES="$(shell ls)"

Namun, setiap baris dijalankan oleh shell terpisah, jadi variabel ini tidak akan bertahan hingga baris berikutnya, jadi Anda harus segera menggunakannya:

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

Ini semua agak konyol karena shell akan mengembang *(dan ekspresi shell glob lainnya) untuk Anda sejak awal, jadi Anda bisa:

        echo *

sebagai perintah shell Anda.

Akhirnya, sebagai aturan umum (tidak benar-benar berlaku untuk contoh ini): seperti catatan esperanto dalam komentar, menggunakan keluaran dari lstidak sepenuhnya dapat diandalkan (beberapa detail bergantung pada nama file dan terkadang bahkan versi ls; beberapa versi lsupaya untuk membersihkan keluaran dalam beberapa kasus). Jadi, sebagai l0b0 dan catatan idelic , jika Anda menggunakan GNU make, Anda dapat menggunakan $(wildcard)dan $(subst ...)menyelesaikan semua yang ada di dalamnya make(menghindari masalah "karakter aneh dalam nama file"). (Dalam shskrip, termasuk bagian resep makefile, metode lain digunakan find ... -print0 | xargs -0untuk menghindari tersandung kosong, baris baru, karakter kontrol, dan sebagainya.)


1 GNU Make mencatat lebih lanjut bahwa POSIX membuat ::=tugas tambahan pada tahun 2012 . Saya belum menemukan tautan referensi cepat ke dokumen POSIX untuk ini, saya juga tidak tahu langsung makevarian mana yang mendukung ::=penugasan, meskipun GNU membuat hari ini, dengan arti yang sama seperti :=, yaitu, melakukan penugasan sekarang dengan perluasan.

Catatan itu VAR := $(shell command args...)juga bisa dieja VAR != command args...dalam beberapa makevarian, termasuk semua varian GNU dan BSD modern sejauh yang saya tahu. Varian lain ini tidak memiliki $(shell)penggunaan sehingga VAR != command args...lebih unggul baik karena lebih pendek dan bekerja di lebih banyak varian.

torek
sumber
Terima kasih. Saya ingin menggunakan perintah yang rumit ( lsdengan seddan cut, misalnya) dan kemudian menggunakan hasilnya di rsync dan perintah lainnya. Apakah saya harus mengulangi perintah yang panjang itu berulang kali? Tidak dapatkah saya menyimpan hasil dalam variabel Make internal?
Adam Matan
1
Gnu make mungkin punya cara untuk melakukan itu, tapi saya belum pernah menggunakannya, dan semua makefile yang sangat rumit yang kita gunakan hanya menggunakan variabel shell dan perintah shell satu baris raksasa yang dibangun dengan "; \" di akhir setiap baris sebagai dibutuhkan. (tidak bisa mendapatkan pengkodean kode untuk bekerja dengan urutan garis miring terbalik di sini, hmm)
torek
1
Mungkin sesuatu seperti: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris
2
@William: makebisa melakukan itu tanpa menggunakan shell: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic
1
Saya ingin menunjukkan bahwa meskipun dalam kasus ini tampaknya tidak terlalu penting, Anda tidak boleh secara otomatis mengurai keluaran ls. ls dimaksudkan untuk menunjukkan informasi kepada manusia, bukan untuk dirantai dalam skrip. Info lebih lanjut di sini: mywiki.wooledge.org/ParsingLs Mungkin, jika 'make' tidak menawarkan ekspansi wildcard yang sesuai, "temukan" lebih baik daripada "ls".
Raúl Salinas-Monteagudo
53

Selain itu, selain jawaban torek: satu hal yang menonjol adalah Anda menggunakan tugas makro yang dievaluasi secara malas.

Jika Anda menggunakan GNU Make, gunakan :=tugas sebagai ganti =. Penugasan ini menyebabkan sisi kanan segera diperluas, dan disimpan di variabel kiri.

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

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

Jika Anda menggunakan =penugasan, itu berarti bahwa setiap kemunculan $(FILES)akan memperluas $(shell ...)sintaks dan dengan demikian menjalankan perintah shell. Ini akan membuat pekerjaan Anda berjalan lebih lambat, atau bahkan memiliki beberapa konsekuensi yang mengejutkan.

Kaz
sumber
1
Sekarang setelah kita memiliki daftar, bagaimana kita mengulang setiap item dalam daftar dan menjalankan perintah di atasnya? Seperti membangun atau menguji?
anon58192932
3
@ anon58192932 Itu khusus iterasi, untuk menjalankan perintah, biasanya dilakukan dalam sebuah fragmen shell sintaks dalam resep membangun, sehingga terjadi di shell, bukan di make: for x in $(FILES); do command $$x; done. Perhatikan doubled up $$yang mengirimkan satu $ke shell. Selain itu, fragmen shell adalah satu baris; untuk menulis kode shell multi baris, anda menggunakan kontinuitas garis miring terbalik yang diproses dengan makesendirinya dan dilipat menjadi satu baris. Yang berarti titik koma pemisah perintah shell bersifat wajib.
Kaz