คำสั่ง Multiline bash ใน makefile


121

ฉันมีวิธีที่สะดวกสบายมากในการรวบรวมโปรเจ็กต์ของฉันผ่านคำสั่ง bash สองสามบรรทัด แต่ตอนนี้ฉันต้องรวบรวมผ่าน makefile เมื่อพิจารณาว่าทุกคำสั่งทำงานในเชลล์ของตัวเองคำถามของฉันคืออะไรคือวิธีที่ดีที่สุดในการเรียกใช้คำสั่ง bash แบบหลายบรรทัดโดยขึ้นอยู่กับกันและกันใน makefile ตัวอย่างเช่นนี้:

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

นอกจากนี้ใครบางคนสามารถอธิบายได้ว่าทำไมแม้แต่คำสั่งบรรทัดเดียวจึงbash -c 'a=3; echo $a > file'ทำงานได้อย่างถูกต้องในเทอร์มินัล แต่สร้างไฟล์เปล่าใน makefile case?


4
ส่วนที่สองควรเป็นคำถามแยกต่างหาก การเพิ่มคำถามอื่นจะทำให้เกิดเสียงดัง
l0b0

คำตอบ:


137

คุณสามารถใช้แบ็กสแลชสำหรับความต่อเนื่องของบรรทัด อย่างไรก็ตามโปรดทราบว่าเชลล์ได้รับคำสั่งทั้งหมดที่เชื่อมต่อกันเป็นบรรทัดเดียวดังนั้นคุณต้องยุติบางบรรทัดด้วยอัฒภาค:

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

แต่ถ้าคุณเพียงแค่ต้องการรับรายการทั้งหมดที่ส่งคืนโดยการfindเรียกใช้และส่งต่อไปgccคุณไม่จำเป็นต้องมีคำสั่งหลายบรรทัด:

foo:
    gcc `find`

หรือใช้วิธีการแบบเชลล์มากขึ้น$(command)(สังเกตการ$หลบหนี):

foo:
    gcc $$(find)

2
สำหรับ multiline คุณยังคงต้องหลีกเลี่ยงสัญญาณดอลลาร์ตามที่ระบุไว้ในความคิดเห็นที่อื่น
tripleee

1
ใช่คำตอบสั้น ๆ 1. $: = $$ 2. ต่อท้าย multiline ด้วย \ BTW bash พวกมักจะแนะนำให้แทนที่ `find` โทรด้วย $$ (find)
Yauhen Yakimovich

89

ตามที่ระบุไว้ในคำถามที่ทุกคำสั่งย่อยจะดำเนินการในเปลือกของตัวเอง สิ่งนี้ทำให้การเขียนเชลล์สคริปที่ไม่สำคัญค่อนข้างยุ่งแต่ก็เป็นไปได้! วิธีแก้ปัญหาคือการรวมสคริปต์ของคุณเข้ากับสิ่งที่ทำให้จะพิจารณาคำสั่งย่อยเดียว (บรรทัดเดียว)

เคล็ดลับในการเขียนเชลล์สคริปต์ภายใน makefiles:

  1. หลีกเลี่ยงการใช้สคริปต์$โดยแทนที่ด้วย$$
  2. แปลงสคริปต์ให้ทำงานเป็นบรรทัดเดียวโดยการแทรก;ระหว่างคำสั่ง
  3. หากคุณต้องการเขียนสคริปต์ในหลายบรรทัดให้หลีกเลี่ยงจุดสิ้นสุดของบรรทัดด้วย \
  4. เลือกเริ่มต้นด้วยset -eเพื่อให้ตรงกับข้อกำหนดของ make เพื่อยกเลิกเมื่อคำสั่งย่อยล้มเหลว
  5. นี่เป็นทางเลือกโดยสิ้นเชิง แต่คุณสามารถยึดสคริปต์ด้วย()หรือ{}เพื่อเน้นความสอดคล้องกันของลำดับบรรทัดหลาย ๆ บรรทัดซึ่งนี่ไม่ใช่ลำดับคำสั่ง makefile ทั่วไป

นี่คือตัวอย่างที่ได้รับแรงบันดาลใจจาก 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 ;\
    }

4
นอกจากนี้คุณยังอาจต้องการSHELL := /bin/bashใน Makefile ของคุณเพื่อให้ทุบตีเฉพาะคุณสมบัติเช่นกระบวนการทดแทน
Brent Bradburn

8
จุดที่ละเอียดอ่อน: ช่องว่างหลัง{เป็นสิ่งสำคัญในการป้องกันการตีความ{setว่าเป็นคำสั่งที่ไม่รู้จัก
Brent Bradburn

3
เมตตาจุด # 2: ใน Makefile มันอาจจะไม่ได้เรื่อง แต่ความแตกต่างระหว่างการตัดด้วย{}และ()ทำให้ความแตกต่างใหญ่ถ้าบางครั้งคุณต้องการคัดลอกสคริปต์และเรียกใช้งานได้โดยตรงจากเปลือกพรอมต์ คุณสามารถสร้างความหายนะเช่นเปลือกของคุณด้วยการประกาศตัวแปรและโดยเฉพาะอย่างยิ่งการปรับเปลี่ยนของรัฐที่มีอยู่ภายในของ set ป้องกันไม่ให้สคริปต์แก้ไขสภาพแวดล้อมของคุณซึ่งอาจเป็นที่ต้องการ ตัวอย่าง ( นี้จะสิ้นสุดเซสชันเปลือกของคุณ ): {}(){ set -e ; } ; false
Brent Bradburn

คุณสามารถใส่ความคิดเห็นโดยใช้แบบฟอร์มต่อไปนี้: command ; ## my comment \` (the comment is between ; `และ` \ `) ดูเหมือนว่าจะทำงานได้ดียกเว้นว่าหากคุณรันคำสั่งด้วยตนเอง (โดยการคัดลอกและวาง) ประวัติคำสั่งจะรวมความคิดเห็นในลักษณะที่ทำลายคำสั่ง (หากคุณพยายามใช้ซ้ำ) [หมายเหตุ: การเน้นไวยากรณ์ใช้งานไม่ได้สำหรับความคิดเห็นนี้เนื่องจากการใช้แบ็กสแลชในแบ็คทิก]
เบรนต์แบรดเบิร์น

หากคุณต้องการทำลายสตริงใน bash / perl / script ภายใน makefile ให้ปิดเครื่องหมายคำพูดสตริงเครื่องหมายแบ็กสแลชขึ้นบรรทัดใหม่ (ไม่มีการเยื้อง) ตัวอย่าง; perl -e QUOTE พิมพ์ 1; QUOTE BACKSLASH NEWLINE QUOTE print 2 QUOTE
mosh

2

มีอะไรผิดปกติกับการเรียกใช้คำสั่ง?

foo:
       echo line1
       echo line2
       ....

และสำหรับคำถามที่สองของคุณคุณจำเป็นที่จะหลบหนี$โดยใช้แทนคือ$$bash -c '... echo $$a ...'

แก้ไข: ตัวอย่างของคุณสามารถเขียนใหม่เป็นสคริปต์บรรทัดเดียวได้ดังนี้:

gcc $(for i in `find`; do echo $i; done)

5
สาเหตุ "ทุกคำสั่งรันในเชลล์ของตัวเอง" ในขณะที่ฉันต้องใช้ผลลัพธ์ของ command1 ใน command2 เหมือนในตัวอย่าง.
Jofsey

หากคุณต้องการเผยแพร่ข้อมูลระหว่างการเรียกใช้เชลล์คุณจำเป็นต้องใช้ที่เก็บข้อมูลภายนอกบางรูปแบบเช่นไฟล์ชั่วคราว แต่คุณสามารถเขียนโค้ดของคุณใหม่ได้ตลอดเวลาเพื่อเรียกใช้คำสั่งหลายคำสั่งในเชลล์เดียวกัน
JesperE

+1 โดยเฉพาะอย่างยิ่งสำหรับรุ่นที่เรียบง่าย และไม่เหมือนกับการทำเพียงแค่ `` gcc `find```?
Eldar Abusalimov

1
ใช่แล้ว. แต่แน่นอนว่าคุณสามารถทำสิ่งที่ซับซ้อนได้มากกว่าแค่ 'เสียงสะท้อน'
JesperE

2

แน่นอนวิธีที่เหมาะสมในการเขียน Makefile คือการจัดทำเอกสารเป้าหมายที่ขึ้นอยู่กับแหล่งที่มา ในกรณีเล็กน้อยวิธีแก้ปัญหาที่เสนอจะfooขึ้นอยู่กับตัวมันเอง แต่แน่นอนว่าmakeฉลาดพอที่จะละทิ้งการพึ่งพาแบบวงกลม แต่ถ้าคุณเพิ่มไฟล์ชั่วคราวลงในไดเรกทอรีของคุณไฟล์นั้นจะกลายเป็นส่วนหนึ่งของห่วงโซ่การพึ่งพา ดีกว่าที่จะสร้างรายการการอ้างอิงที่ชัดเจนครั้งแล้วครั้งเล่าโดยอาจใช้สคริปต์

GNU ทำให้รู้ว่าต้องวิ่งอย่างไร gccเพื่อสร้างไฟล์ปฏิบัติการจากชุด.cและ.hไฟล์ดังนั้นบางทีทั้งหมดที่คุณต้องการจริงๆ

foo: $(wildcard *.h) $(wildcard *.c)

1

คำสั่ง ONESHELL อนุญาตให้เขียนสูตรอาหารหลายบรรทัดเพื่อดำเนินการในการเรียกใช้เชลล์เดียวกัน

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

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

มีข้อเสียเปรียบ: อักขระนำหน้าพิเศษ ('@', '-' และ '+') จะตีความแตกต่างกัน

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.