วิธีใช้คำสั่งเชลล์ใน Makefile


102

ฉันพยายามใช้ผลลัพธ์ของlsคำสั่งอื่น ๆ (เช่น echo, rsync):

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

แต่ฉันได้รับ:

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

ฉันได้พยายามใช้echo $$FILES, echo ${FILES}และecho $(FILES)มีโชคไม่

คำตอบ:


157

ด้วย:

FILES = $(shell ls)

เยื้องใต้allแบบนั้นมันเป็นคำสั่งสร้าง ดังนั้นการขยายตัวนี้แล้วพยายามที่จะเรียกใช้คำสั่ง$(shell ls)FILES ...

หากFILESควรเป็นmakeตัวแปรต้องกำหนดตัวแปรเหล่านี้นอกส่วนของสูตรอาหารเช่น:

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

แน่นอนว่านั่นหมายความว่าFILESจะถูกตั้งค่าเป็น "เอาต์พุตจากls" ก่อนที่จะรันคำสั่งใด ๆ ที่สร้างไฟล์. tgz (แม้ว่าในขณะที่Kaz ตั้งข้อสังเกตว่าตัวแปรจะถูกขยายใหม่ทุกครั้งดังนั้นในที่สุดก็จะรวมไฟล์. tgz บางตัวทำให้ตัวแปรต้องFILES := ...หลีกเลี่ยงสิ่งนี้เพื่อประสิทธิภาพและ / หรือความถูกต้อง1 )

หากFILESควรเป็นตัวแปรเชลล์คุณสามารถตั้งค่าได้ แต่ต้องทำในเชลล์เอสโดยไม่เว้นวรรคและยกมา:

all:
        FILES="$(shell ls)"

อย่างไรก็ตามแต่ละบรรทัดถูกเรียกใช้โดยเชลล์ที่แยกจากกันดังนั้นตัวแปรนี้จะไม่อยู่รอดในบรรทัดถัดไปดังนั้นคุณต้องใช้มันทันที:

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

ทั้งหมดนี้เป็นเรื่องงี่เง่าเล็กน้อยเนื่องจากเชลล์จะขยาย*(และนิพจน์เชลล์ลูกโลกอื่น ๆ ) ให้คุณในตอนแรกดังนั้นคุณสามารถ:

        echo *

เป็นคำสั่งเชลล์ของคุณ

สุดท้ายตามกฎทั่วไป (ใช้ไม่ได้กับตัวอย่างนี้): ตามที่esperantoบันทึกไว้ในความคิดเห็นการใช้เอาต์พุตจากlsไม่น่าเชื่อถืออย่างสมบูรณ์ (รายละเอียดบางอย่างขึ้นอยู่กับชื่อไฟล์และบางครั้งแม้แต่เวอร์ชันของlsบางเวอร์ชันlsพยายามล้างเอาต์พุต ในบางกรณี). ดังนั้นในฐานะที่เป็นl0b0และidelic note หากคุณใช้ GNU ทำให้คุณสามารถใช้$(wildcard)และ$(subst ...)ทำทุกอย่างภายในmakeตัวเองให้สำเร็จ(หลีกเลี่ยงปัญหา "อักขระแปลก ๆ ในชื่อไฟล์") (ในshสคริปต์รวมถึงส่วนสูตรของ makefiles อีกวิธีหนึ่งคือใช้find ... -print0 | xargs -0เพื่อหลีกเลี่ยงการสะดุดบนช่องว่างบรรทัดใหม่อักขระควบคุมและอื่น ๆ )


1 ลิขสิทธิ์ GNU บันทึกเอกสารยี่ห้อต่อไปว่า POSIX ทำให้เพิ่ม::=การกำหนดในปี 2012 ฉันไม่พบลิงก์อ้างอิงด่วนไปยังเอกสาร POSIX สำหรับสิ่งนี้และฉันไม่รู้ว่าตัวเลือกใดบ้างที่makeรองรับการ::=มอบหมายงานแม้ว่า GNU จะทำในวันนี้ แต่ก็มีความหมายเดียวกัน:=คือทำการมอบหมายทันทีด้วยการขยาย

โปรดทราบว่าVAR := $(shell command args...)สามารถสะกดได้VAR != command args...หลายmakeรูปแบบรวมถึงรูปแบบ GNU และ BSD ที่ทันสมัยทั้งหมดเท่าที่ฉันรู้ สายพันธุ์อื่น ๆ เหล่านี้ไม่มี$(shell)ดังนั้นการใช้งานVAR != command args...จึงดีกว่าทั้งในด้านที่สั้นกว่าและทำงานในตัวแปรอื่น ๆ


ขอบคุณ. ฉันต้องการใช้คำสั่งที่ซับซ้อน (เช่นlsด้วยsedและตัด) จากนั้นใช้ผลลัพธ์ใน rsync และคำสั่งอื่น ๆ ฉันต้องพูดคำสั่งยาว ๆ ซ้ำแล้วซ้ำเล่า? ฉันไม่สามารถเก็บผลลัพธ์ไว้ในตัวแปร Make ภายในได้หรือไม่?
Adam Matan

1
Gnu make อาจมีวิธีทำเช่นนั้น แต่ฉันไม่เคยใช้มันและ makefiles ที่ซับซ้อนอย่างน่ากลัวทั้งหมดเราใช้เพียงแค่ใช้ตัวแปรเชลล์และคำสั่งเชลล์ซับเดียวขนาดยักษ์ที่สร้างด้วย "; \" ที่ท้ายแต่ละบรรทัดเป็น จำเป็น (ไม่สามารถรับการเข้ารหัสโค้ดเพื่อทำงานกับลำดับแบ็กสแลชได้ที่นี่อืม)
torek

1
อาจจะมีบางอย่างเช่น FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris

2
@ วิลเลียม: makeทำได้โดยไม่ต้องใช้เชลล์: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic

1
ฉันอยากจะชี้ให้เห็นว่าแม้ว่าในกรณีนี้ดูเหมือนจะไม่สำคัญมากนัก แต่คุณไม่ควรแยกวิเคราะห์ผลลัพธ์ของ ls โดยอัตโนมัติ ls มีขึ้นเพื่อแสดงข้อมูลแก่มนุษย์ไม่ใช่ผูกมัดในสคริปต์ ข้อมูลเพิ่มเติมที่นี่: mywiki.wooledge.org/ParsingLs อาจเป็นไปได้ว่าในกรณีที่ "make" ไม่ได้เสนอการขยายสัญลักษณ์แทน "find" จะดีกว่า "ls"
Raúl Salinas-Monteagudo

53

นอกจากนี้นอกเหนือจากคำตอบของ torek แล้วสิ่งหนึ่งที่โดดเด่นคือคุณกำลังใช้การกำหนดมาโครที่ประเมินค่าอย่างเฉื่อยชา

หากคุณใช้ GNU Make ให้ใช้ไฟล์ :==ที่ได้รับมอบหมายแทน การกำหนดนี้ทำให้ด้านขวามือถูกขยายทันทีและเก็บไว้ในตัวแปรด้านซ้ายมือ

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

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

หากคุณใช้การ=กำหนดหมายความว่าทุกครั้งที่เกิดขึ้น$(FILES)จะขยาย$(shell ...)ไวยากรณ์และเรียกใช้คำสั่งเชลล์ สิ่งนี้จะทำให้งานของคุณทำงานช้าลงหรือแม้กระทั่งผลที่น่าประหลาดใจ


1
ตอนนี้เรามีรายการแล้วเราจะทำซ้ำแต่ละรายการในรายการและดำเนินการคำสั่งได้อย่างไร? เช่นสร้างหรือทดสอบ?
anon58192932

3
@ anon58192932 for x in $(FILES); do command $$x; doneที่ซ้ำที่เฉพาะเจาะจงในการดำเนินการคำสั่งมักจะทำในส่วนเปลือกไวยากรณ์ในการสร้างสูตรเพื่อให้มันเกิดขึ้นในเปลือกมากกว่าในการทำ: สังเกตการเพิ่มเป็นสองเท่า$$ซึ่งส่งผ่านซิงเกิล$ไปยังเชลล์ นอกจากนี้เศษเปลือกยังเป็นหนึ่งใน ในการเขียนโค้ดเชลล์แบบหลายบรรทัดคุณใช้ความต่อเนื่องของแบ็กสแลชที่ประมวลผลด้วยmakeตัวเองและพับเป็นบรรทัดเดียว ซึ่งหมายความว่าอัฒภาคที่แยกคำสั่งเชลล์มีผลบังคับใช้
Kaz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.