Bagaimana cara menulis perintah 'cd' di makefile?

354

Sebagai contoh, saya memiliki sesuatu seperti ini di makefile saya:

all:
     cd some_directory

Tetapi ketika saya mengetik makesaya hanya melihat 'cd some_directory', seperti pada echoperintah.

Davit Siradeghyan
sumber
5
Tidak jelas apa yang ingin Anda lakukan, tetapi, dalam pengalaman saya dengan make, saya tidak pernah ingin mengubah direktori seperti ini. Mungkin Anda harus mencoba pendekatan lain untuk solusi Anda?
P Shved
2
Adalah kesalahan pemula yang umum untuk meyakini direktori Anda penting. Untuk kebanyakan hal itu tidak; cd dir; cmd filehampir selalu dapat lebih bermanfaat dinyatakan sebagai cmd dir/file.
tripleee
34
Ini adalah kesalahan pemula yang umum untuk percaya direktori kerja Anda saat ini tidak penting. Banyak program, terutama skrip shell, ditulis dengan nilai tertentu .dalam pikiran. Memang benar bahwa sebagian besar alat dirancang sedemikian rupa sehingga Anda tidak perlu mengubah pwd Anda untuk itu. Tapi ini tidak selalu benar, dan saya pikir itu bukan ide yang baik untuk menyebutnya "kesalahan" untuk percaya direktori Anda mungkin penting.
Evelyn Kokemoor
Tip: Jika perintah cd Anda mengatakan "Tidak ada berkas atau direktori", bahkan ketika (relatif) direktori tidak ada, cek yang variabel lingkungan CDPATH Anda baik kosong atau termasuk "". Lakukan perintah eksekusi dengan "sh", yang hanya akan menemukan jalur relatif melalui CDPATH jika diset. Ini kontras dengan bash, yang akan mencoba. sebelum berkonsultasi dengan CDPATH.
Denis Howe

Jawaban:

621

Ini sebenarnya mengeksekusi perintah, mengubah direktori untuk some_directory, bagaimanapun, ini dilakukan dalam shell sub-proses, dan tidak memengaruhi make atau shell yang Anda gunakan.

Jika Anda ingin melakukan lebih banyak tugas di dalam some_directory, Anda perlu menambahkan titik koma dan menambahkan perintah lain juga. Perhatikan bahwa Anda tidak dapat menggunakan baris baru karena mereka ditafsirkan oleh make sebagai akhir dari aturan, sehingga baris baru yang Anda gunakan untuk kejelasan harus diloloskan oleh backslash.

Sebagai contoh:

all:
        cd some_dir; echo "I'm in some_dir"; \
          gcc -Wall -o myTest myTest.c

Perhatikan juga bahwa titik koma diperlukan di antara setiap perintah meskipun Anda menambahkan garis miring terbalik dan baris baru. Ini disebabkan oleh fakta bahwa seluruh string diurai sebagai satu baris oleh shell. Seperti disebutkan dalam komentar, Anda harus menggunakan '&&' untuk bergabung dengan perintah, yang berarti mereka hanya dieksekusi jika perintah sebelumnya berhasil.

all:
        cd some_dir && echo "I'm in some_dir" && \
          gcc -Wall -o myTest myTest.c

Ini sangat penting ketika melakukan pekerjaan yang merusak, seperti pembersihan, karena Anda akan menghancurkan barang-barang yang salah, jika cdgagal karena alasan apa pun.

Penggunaan yang umum adalah memanggil make di sub direktori, yang mungkin Anda ingin tinjau. Ada opsi baris perintah untuk ini sehingga Anda tidak perlu memanggil cddiri sendiri, sehingga aturan Anda akan terlihat seperti ini

all:
        $(MAKE) -C some_dir all

yang akan berubah menjadi some_dirdan mengeksekusi Makefiledi sana dengan target "semua". Sebagai praktik terbaik, gunakan $(MAKE)alih-alih memanggil makelangsung, karena akan berhati-hati untuk memanggil instance instan yang tepat (jika Anda, misalnya, menggunakan versi make khusus untuk lingkungan build Anda), serta memberikan perilaku yang sedikit berbeda saat menjalankan menggunakan sakelar tertentu, seperti -t.

Sebagai catatan, buat selalu gema perintah yang dijalankannya (kecuali ditekan secara eksplisit), bahkan jika itu tidak memiliki output, yang adalah apa yang Anda lihat.

Falstro
sumber
8
Ya tidak selalu . Untuk menekan gema, cukup masukkan @ di awal baris.
Beta
2
@ Beta: baik ya, dan awalan tanda hubung mengabaikan status kesalahan juga. Mungkin saya sedikit terhanyut, saya ingin menunjukkan fakta bahwa make tidak mengulangi perintah, terlepas dari jenis perintahnya. Dan dalam hal ini, itu adalah perintah tanpa output, yang membuat gema tampak lebih asing bagi seseorang yang tidak terbiasa dengan make.
falstro
46
Dua nits: 1. Perintah-perintah harus benar-benar bergabung &&, karena dengan ;jika direktori tidak ada dan cdgagal, shell akan terus menjalankan sisa perintah di direktori saat ini, yang dapat menyebabkan hal-hal seperti "file tidak misterius" menemukan ”pesan untuk dikompilasi, loop tak terbatas saat menjalankan make, atau bencana seperti aturan clean:: cd dir; rm -rf *. 2. Saat menjalankan sub-merek, panggil $(MAKE)alih-alih makeagar opsi akan diteruskan dengan benar .
andrewdotn
2
@perreal, saya biasanya mendefinisikan aturan pola seperti: %-recursive:with the body: @T="$@";$(MAKE) -C some_dir $${T%-*}(Saya biasanya punya for-loop juga, untuk mengulang daftar subdirs, $${T%-*}ini adalah ekspansi bash yang menghilangkan -recursivebagian dari nama target) dan kemudian mendefinisikan eksplisit tangan pendek (dan .PHONY) target untuk masing-masing, seperti all: all-recursive, check: check-recursive, clean: clean-recursive.
Falstro
1
@ChristianStewart, benar, seperti yang disebutkan dalam komentar 2 dan 3.
falstro
95

Mulai dari GNU make 3.82 (Juli 2010), Anda dapat menggunakan .ONESHELLtarget khusus untuk menjalankan semua baris resep dalam satu instantiasi shell (bold penekanan tambang):

  • Sasaran khusus baru: .ONESHELLmenginstruksikan membuat untuk memohon satu contoh dari shell dan memberikan dengan seluruh resep , terlepas dari berapa banyak baris yang dikandungnya. [...]
.ONESHELL: # Only applies to all target
all:
    cd ~/some_dir
    pwd # Prints ~/some_dir if cd succeeded
Chnossos
sumber
6
Perhatikan saja bahwa pwditu berfungsi, juga `pwd`(dengan backticks), tetapi $(shell pwd)dan $(PWD)masih akan mengembalikan direktori sebelum melakukan cdperintah, sehingga Anda tidak dapat menggunakannya secara langsung.
anol
3
Ya karena ekspansi variabel dan fungsi dilakukan sebelum mengeksekusi perintah oleh make, sedangkan pwddan `pwd`dieksekusi oleh shell itu sendiri.
Chnossos
2
Tidak semua orang bekerja dengan makefile warisan, dan bahkan kemudian jawaban ini adalah tentang mengetahui kemungkinan ini ada.
Chnossos
1
Ini bisa menjadi pilihan yang menjengkelkan / berbahaya karena hanya perintah terakhir untuk target yang dapat menyebabkan kegagalan (setiap kegagalan perintah sebelumnya akan diabaikan), dan mungkin tidak ada yang bekerja dengan Anda akan mengharapkan itu.
tobii
1
Setel SHELLFLAGSke -e -cdan shell akan keluar pada perintah pertama yang gagal.
Timothy Baldwin
21

Inilah trik lucu untuk berurusan dengan direktori dan membuatnya. Alih-alih menggunakan string multiline, atau "cd;" pada setiap perintah, tentukan fungsi chdir sederhana sebagai berikut:

CHDIR_SHELL := $(SHELL)
define chdir
   $(eval _D=$(firstword $(1) $(@D)))
   $(info $(MAKE): cd $(_D)) $(eval SHELL = cd $(_D); $(CHDIR_SHELL))
endef

Maka yang harus Anda lakukan adalah menyebutnya dalam aturan Anda sebagai berikut:

all:
          $(call chdir,some_dir)
          echo "I'm now always in some_dir"
          gcc -Wall -o myTest myTest.c

Anda bahkan dapat melakukan hal berikut:

some_dir/myTest:
          $(call chdir)
          echo "I'm now always in some_dir"
          gcc -Wall -o myTest myTest.c
JoeS
sumber
41
"Imut"? Lebih seperti cukup tali untuk menembak diri sendiri di kaki.
tripleee
1
Apakah direktori saat ini kemudian ditetapkan untuk perintah hanya dalam aturan itu, atau untuk semua aturan selanjutnya yang dijalankan? Juga, akankah beberapa variasi ini berfungsi di Windows?
user117529
8
Ini tentu saja istirahat untuk eksekusi paralel ( -jn), yang merupakan inti dari make really.
bobbogo
5
Ini adalah hack yang buruk. Jika Anda harus menggunakan hal seperti itu, Anda tidak menggunakan Makefiles untuk apa tujuan mereka.
gatopeich
4
Saya tidak akan setuju bahwa ini adalah peretasan yang buruk. Tetapi, tunjukkanlah beberapa hal jahat yang dapat Anda lakukan.
JoeS
9

Apa yang ingin Anda lakukan begitu sampai di sana? Setiap perintah dieksekusi dalam subkulit, sehingga subkulit tersebut mengubah direktori, tetapi hasil akhirnya adalah bahwa perintah berikutnya masih dalam direktori saat ini.

Dengan GNU make, Anda dapat melakukan sesuatu seperti:

BIN=/bin
foo:
    $(shell cd $(BIN); ls)
Nadir SOUALEM
sumber
5
Mengapa $(shell ...)kapan cd $(BIN); lsatau cd $(BIN) && ls(seperti yang ditunjukkan oleh @andrewdotn) sudah cukup.
vapace
1

Untuk mengubah dir

foo: 
    $(MAKE) -C mydir

multi:
    $(MAKE) -C / -C my-custom-dir   ## Equivalent to /my-custom-dir
jackotonye
sumber
-6

Seperti ini:

target:
    $(shell cd ....); \
    # ... commands execution in this directory
    # ... no need to go back (using "cd -" or so)
    # ... next target will be automatically in prev dir

Semoga berhasil!

kubus Rubik
sumber
1
Tidak, ini salah. Secara khusus, $(shell cd ....)dijalankan ketika Makefile awalnya diuraikan, bukan ketika resep khusus ini dijalankan.
tripleee
@ Triplee Tidak cukup - $(shell)diperluas hanya ketika membuat memutuskan untuk membangun target . Jika make tidak pernah membutuhkan resep, itu tidak akan berkembang.
bobbogo