Skrip tunggal untuk dijalankan di batch Windows dan Linux Bash?


96

Apakah mungkin untuk menulis satu file skrip yang dijalankan di Windows (diperlakukan sebagai .bat) dan Linux (melalui Bash)?

Saya tahu sintaks dasar keduanya, tetapi tidak mengetahuinya. Itu mungkin bisa mengeksploitasi sintaksis Bash yang tidak jelas atau beberapa kesalahan prosesor batch Windows.

Perintah yang akan dijalankan mungkin hanya satu baris untuk menjalankan skrip lainnya.

Motivasinya adalah hanya memiliki satu perintah boot aplikasi untuk Windows dan Linux.

Pembaruan: Kebutuhan untuk skrip shell "asli" sistem adalah ia harus memilih versi penerjemah yang tepat, menyesuaikan dengan variabel lingkungan terkenal tertentu, dll. Tidak disarankan untuk menginstal lingkungan tambahan seperti CygWin - Saya ingin mempertahankan konsepnya " unduh & jalankan ".

Satu-satunya bahasa lain yang perlu dipertimbangkan untuk Windows adalah Windows Scripting Host - WSH, yang telah disetel secara default sejak 98.


9
Cari tahu tentang Perl atau Python. Itu adalah bahasa skrip yang tersedia di kedua platform.
a_horse_with_no_name

groovy, python, ruby ​​anyone? stackoverflow.com/questions/257730/…
Kalpesh Soni

Groovy akan sangat bagus untuk dimiliki di semua sistem secara default, saya akan menganggapnya sebagai lang scripting shell. Mungkin suatu hari nanti :)
Ondra Žižka


2
Contoh penggunaan yang menarik untuk ini adalah tutorial - untuk dapat memberi tahu orang-orang "jalankan skrip kecil ini" tanpa harus menjelaskan bahwa "jika Anda menggunakan Windows, gunakan skrip kecil ini, tetapi jika Anda menggunakan Linux atau Mac, coba ini sebagai gantinya, yang sebenarnya belum saya uji karena saya menggunakan Windows. " Sayangnya Windows tidak memiliki perintah dasar seperti unix seperti bawaan cp, atau sebaliknya jadi menulis dua skrip terpisah mungkin secara pedagogis lebih baik daripada teknik lanjutan yang ditunjukkan di sini.
Qwertie

Jawaban:


92

Apa yang telah saya lakukan adalah menggunakan sintaks label cmd sebagai penanda komentar . Karakter label, titik dua ( :), sama dengan truedi kebanyakan shell POSIXish. Jika Anda segera mengikuti karakter label dengan karakter lain yang tidak dapat digunakan dalam a GOTO, maka mengomentari cmdskrip Anda tidak akan memengaruhi cmdkode Anda .

Retasannya adalah dengan meletakkan baris kode setelah urutan karakter " :;". Jika Anda kebanyakan menulis skrip satu baris atau, seperti yang mungkin terjadi, dapat menulis satu baris shuntuk banyak baris cmd, berikut ini mungkin baik-baik saja. Jangan lupa bahwa penggunaan apa pun $?harus sebelum titik dua berikutnya :karena :disetel ulang $?ke 0.

:; echo "Hi, I’m ${SHELL}."; exit $?
@ECHO OFF
ECHO I'm %COMSPEC%

Contoh penjagaan yang sangat dibuat-buat $?:

:; false; ret=$?
:; [ ${ret} = 0 ] || { echo "Program failed with code ${ret}." >&2; exit 1; }
:; exit
ECHO CMD code.

Ide lain untuk melompati cmdkode adalah dengan menggunakan heredocs sehingga shmemperlakukan cmdkode tersebut sebagai string yang tidak digunakan dan cmdmenafsirkannya. Dalam kasus ini, kami memastikan bahwa pembatas heredoc kami dikutip (untuk berhenti shmelakukan interpretasi apa pun pada kontennya saat menjalankan dengan sh) dan memulai dengan :sehingga cmdmelompati seperti baris lain yang dimulai :.

:; echo "I am ${SHELL}"
:<<"::CMDLITERAL"
ECHO I am %COMSPEC%
::CMDLITERAL
:; echo "And ${SHELL} is back!"
:; exit
ECHO And back to %COMSPEC%

Bergantung pada kebutuhan atau gaya pengkodean Anda, interlace cmddan shkode mungkin masuk akal atau tidak. Menggunakan heredocs adalah salah satu metode untuk melakukan interlace tersebut. Namun, ini dapat diperpanjang dengan GOTOteknik :

:<<"::CMDLITERAL"
@ECHO OFF
GOTO :CMDSCRIPT
::CMDLITERAL

echo "I can write free-form ${SHELL} now!"
if :; then
  echo "This makes conditional constructs so much easier because"
  echo "they can now span multiple lines."
fi
exit $?

:CMDSCRIPT
ECHO Welcome to %COMSPEC%

Komentar universal, tentu saja, dapat dilakukan dengan urutan karakter : #atau :;#. Spasi atau titik koma diperlukan karena shdianggap #sebagai bagian dari nama perintah jika bukan karakter pertama pengenal. Misalnya, Anda mungkin ingin menulis komentar universal di baris pertama file Anda sebelum menggunakan GOTOmetode untuk membagi kode Anda. Kemudian Anda dapat memberi tahu pembaca mengapa skrip Anda ditulis dengan sangat aneh:

: # This is a special script which intermixes both sh
: # and cmd code. It is written this way because it is
: # used in system() shell-outs directly in otherwise
: # portable code. See /programming/17510688
: # for details.
:; echo "This is ${SHELL}"; exit
@ECHO OFF
ECHO This is %COMSPEC%

Jadi, beberapa ide dan cara untuk menyelesaikan shdan cmdskrip -compatible tanpa efek samping yang serius sejauh yang saya tahu (dan tanpa cmdoutput '#' is not recognized as an internal or external command, operable program or batch file.).


5
Perhatikan ini membutuhkan skrip untuk memiliki .cmdekstensi, jika tidak maka tidak akan berjalan di Windows. Apakah ada solusi lain?
jviotti

1
Jika Anda menulis file batch, saya akan merekomendasikan hanya menamainya .cmddan menandainya dapat dieksekusi. Dengan begitu, menjalankan skrip dari shell default di Windows atau unix akan berfungsi (hanya diuji dengan bash). Karena saya tidak dapat memikirkan cara untuk memasukkan shebang tanpa membuat cmd mengeluh dengan keras, Anda harus selalu memanggil skrip melalui shell. Yaitu, pastikan untuk menggunakan execlp()atau execvp()(menurut saya system()melakukan ini dengan cara yang "benar" untuk Anda) daripada execl()atau execv()(fungsi terakhir ini hanya akan bekerja pada skrip dengan shebang karena mereka lebih langsung ke OS).
binki

Saya menghilangkan diskusi tentang ekstensi file karena saya menulis kode portabel saya di shellouts (misalnya, <Exec/>MSBuild Task ) daripada sebagai file batch independen. Mungkin jika saya punya waktu di masa depan saya akan mengupdate jawaban dengan informasi di komentar ini…
binki

23

Anda bisa mencoba ini:

#|| goto :batch_part
 echo $PATH
#exiting the bash part
exit
:batch_part
 echo %PATH%

Mungkin Anda perlu menggunakan /r/nsebagai baris baru daripada gaya unix. Jika saya ingat benar, baris baru unix tidak diartikan sebagai baris baru oleh .batskrip. Cara lain adalah dengan membuat #.exefile di jalur yang tidak melakukan apa pun di cara yang mirip dengan jawaban saya di sini: Apakah mungkin untuk menanamkan dan mengeksekusi VBScript dalam file batch tanpa menggunakan file sementara?

EDIT

Jawaban binki hampir sempurna tapi masih bisa ditingkatkan:

:<<BATCH
    @echo off
    echo %PATH%
    exit /b
BATCH

echo $PATH

Ini menggunakan lagi :trik dan komentar multi baris. Sepertinya cmd.exe (setidaknya di windows10) berfungsi tanpa masalah dengan EOL gaya unix jadi pastikan bahwa skrip Anda diubah ke dalam format linux. (pendekatan yang sama telah digunakan sebelumnya di sini dan di sini ). Padahal menggunakan shebang tetap akan menghasilkan keluaran yang mubazir ...


3
/r/ndi akhir baris akan menyebabkan banyak sakit kepala dalam skrip bash kecuali Anda mengakhiri setiap baris dengan #(yaitu #/r/n) - dengan cara itu \rakan diperlakukan sebagai bagian dari komentar dan diabaikan.
Gordon Davisson

2
Sebenarnya, saya menemukan bahwa # 2>NUL & ECHO Welcome to %COMSPEC%.berhasil menyembunyikan kesalahan tentang #perintah yang tidak ditemukan oleh cmd. Teknik ini berguna saat Anda perlu menulis baris mandiri yang hanya akan dieksekusi oleh cmd(mis., Dalam a Makefile).
binki

11

Saya ingin berkomentar, tetapi hanya dapat menambahkan jawaban saat ini.

Teknik yang diberikan sangat bagus dan saya juga menggunakannya.

Sulit untuk menyimpan file yang memiliki dua jenis jeda baris yang terkandung di dalamnya, yaitu /nuntuk bagian bash dan/r/n untuk bagian windows. Kebanyakan editor mencoba dan menerapkan skema baris baru dengan menebak jenis file yang Anda edit. Juga sebagian besar metode mentransfer file melalui internet (terutama sebagai file teks atau skrip) akan mencuci jeda baris, jadi Anda bisa mulai dengan satu jenis jeda baris dan berakhir dengan yang lain. Jika Anda membuat asumsi tentang jeda baris dan kemudian memberikan skrip Anda kepada orang lain untuk digunakan, mereka mungkin merasa itu tidak berhasil untuk mereka.

Masalah lainnya adalah sistem file yang dipasang di jaringan (atau CD) yang digunakan bersama di antara jenis sistem yang berbeda (terutama di mana Anda tidak dapat mengontrol perangkat lunak yang tersedia untuk pengguna).

Oleh karena itu, seseorang harus menggunakan jeda baris DOS /r/ndan juga melindungi skrip bash dari DOS /rdengan memberi komentar di akhir setiap baris ( #). Anda juga tidak dapat menggunakan kelanjutan baris di bash karena/r akan menyebabkannya putus.

Dengan cara ini siapa pun yang menggunakan skrip, dan dalam lingkungan apa pun, skrip akan berfungsi.

Saya menggunakan metode ini bersama dengan membuat Makefile portabel!


6

Berikut ini berfungsi untuk saya tanpa kesalahan atau pesan kesalahan dengan Bash 4 dan Windows 10, tidak seperti jawaban di atas. Saya memberi nama file "terserah.cmd", lakukan chmod +xagar dapat dieksekusi di linux, dan membuatnya memiliki akhiran baris unix ( dos2unix) untuk menjaga bash tetap tenang.

:; if [ -z 0 ]; then
  @echo off
  goto :WINDOWS
fi

if [ -z "$2" ]; then
  echo "usage: $0 <firstArg> <secondArg>"
  exit 1
fi

# bash stuff
exit

:WINDOWS
if [%2]==[] (
  SETLOCAL enabledelayedexpansion
  set usage="usage: %0 <firstArg> <secondArg>"
  @echo !usage:"=!
  exit /b 1
)

:: windows stuff

3

Anda dapat membagikan variabel:

:;SET() { eval $1; }

SET var=value

:;echo $var
:;exit
ECHO %var%

2

Ada beberapa cara untuk menjalankan perintah yang berbeda pada bashdan cmddengan skrip yang sama.

cmdakan mengabaikan baris yang dimulai dengan :;, seperti yang disebutkan dalam jawaban lain. Ini juga akan mengabaikan baris berikutnya jika baris saat ini diakhiri dengan perintah rem ^, karena ^karakter akan keluar dari jeda baris dan baris berikutnya akan diperlakukan sebagai komentar olehrem .

Sedangkan untuk membuat garis bashabaikan cmd, ada beberapa cara. Saya telah menyebutkan beberapa cara untuk melakukannya tanpa melanggar cmdperintah:

#Perintah tidak ada (tidak disarankan)

Jika tidak ada #perintah yang tersedia cmdsaat skrip dijalankan, kita dapat melakukan ini:

# 2>nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #

The #karakter pada awal cmdmerek garis bashmemperlakukan bahwa garis sebagai komentar.

The #karakter pada akhir bashbaris digunakan untuk komentar pada \rkarakter, seperti Brian Tompsett tunjukkan dalam jawabannya . Tanpa ini, bashakan memunculkan kesalahan jika file memiliki \r\nakhiran baris, diperlukan olehcmd .

Dengan melakukan # 2>nul, kami menipu cmduntuk mengabaikan kesalahan dari beberapa #perintah yang tidak ada , sambil tetap menjalankan perintah yang mengikuti.

Jangan gunakan solusi ini jika ada #perintah yang tersedia di PATHatau jika Anda tidak memiliki kendali atas perintah yang tersedia untuk cmd.


Menggunakan echountuk mengabaikan #karakter dicmd

Kita dapat menggunakan echodengan keluarannya yang diarahkan untuk memasukkan cmdperintah di basharea yang dikomentari:

echo >/dev/null # >nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #

Karena #karakter tidak memiliki arti khusus cmd, itu diperlakukan sebagai bagian dari teks ke echo. Yang harus kita lakukan adalah mengarahkan output dari echoperintah dan memasukkan perintah lain setelahnya.


#.batFile kosong

echo >/dev/null # 1>nul 2> #.bat
# & echo Hello cmd! & del #.bat & rem ^
echo 'Hello bash!' #

The echo >/dev/null # 1>nul 2> #.batgaris menciptakan kosong #.batberkas sementara di cmd(atau mengganti yang sudah ada #.bat, jika ada), dan tidak apa-apa saat bash.

File ini akan digunakan oleh cmdbaris berikutnya meskipun ada beberapa #perintah lain di PATH.

The del #.batperintah pada cmdkode -specific menghapus file yang telah dibuat. Anda hanya perlu melakukan ini di cmdbaris terakhir .

Jangan gunakan solusi ini jika #.batfile mungkin ada di direktori kerja Anda saat ini, karena file itu akan dihapus.


Direkomendasikan: menggunakan here-document untuk mengabaikan cmdperintah dibash

:; echo 'Hello bash!';<<:
echo Hello cmd! & ^
:

Dengan menempatkan ^karakter di akhir cmdbaris, kita keluar dari baris baru, dan dengan menggunakan :sebagai pemisah dokumen di sini, konten baris pembatas tidak akan berpengaruh cmd. Dengan begitu, cmdhanya akan mengeksekusi barisnya setelah :baris berakhir, memiliki perilaku yang sama seperti bash.

Jika Anda ingin memiliki banyak baris di kedua platform dan hanya mengeksekusinya di akhir blok, Anda dapat melakukan ini:

:;( #
  :;  echo 'Hello'  #
  :;  echo 'bash!'  #
:; );<<'here-document delimiter'
(
      echo Hello
      echo cmd!
) & rem ^
here-document delimiter

Selama tidak ada cmdgaris yang tepat here-document delimiter, solusi ini akan berhasil. Anda dapat mengubah here-document delimiterke teks lain.


Di semua solusi yang disajikan, perintah hanya akan dijalankan setelah baris terakhir , membuat perilakunya konsisten jika melakukan hal yang sama di kedua platform.

Solusi tersebut harus disimpan ke file dengan \r\njeda baris, jika tidak, solusi tersebut tidak akan berfungsi cmd.


Lebih baik terlambat daripada tidak sama sekali ... ini membantu saya lebih dari jawaban lainnya. Terima kasih!!
A-Diddy

2

Jawaban sebelumnya tampaknya mencakup hampir semua opsi dan banyak membantu saya. Saya menyertakan jawaban ini di sini hanya untuk mendemonstrasikan mekanisme yang saya gunakan untuk menyertakan skrip Bash dan skrip CMD Windows dalam file yang sama.

LinuxWindowsScript.bat

echo >/dev/null # >nul & GOTO WINDOWS & rem ^
echo 'Processing for Linux'

# ***********************************************************
# * NOTE: If you modify this content, be sure to remove carriage returns (\r) 
# *       from the Linux part and leave them in together with the line feeds 
# *       (\n) for the Windows part. In summary:
# *           New lines in Linux: \n
# *           New lines in Windows: \r\n 
# ***********************************************************

# Do Linux Bash commands here... for example:
StartDir="$(pwd)"

# Then, when all Linux commands are complete, end the script with 'exit'...
exit 0

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

:WINDOWS
echo "Processing for Windows"

REM Do Windows CMD commands here... for example:
SET StartDir=%cd%

REM Then, when all Windows commands are complete... the script is done.

Ringkasan

Di Linux

Baris pertama ( echo >/dev/null # >nul & GOTO WINDOWS & rem ^) akan diabaikan dan skrip akan mengalir melalui setiap baris segera setelahnya sampai exit 0perintah dijalankan. Setelah exit 0tercapai, eksekusi skrip akan berakhir, mengabaikan perintah Windows di bawahnya.

Di Windows

Baris pertama akan menjalankan GOTO WINDOWSperintah, melewati perintah Linux segera setelahnya dan melanjutkan eksekusi pada :WINDOWSbaris tersebut.

Menghapus Carriage Returns di Windows

Karena saya mengedit file ini di Windows, saya harus secara sistematis menghapus carriage return (\ r) dari perintah Linux atau saya mendapat hasil yang tidak normal saat menjalankan bagian Bash. Untuk melakukan ini, saya membuka file di Notepad ++ dan melakukan hal berikut:

  1. Mengaktifkan opsi untuk melihat karakter baris ( View> Show Symbol> Show End of Line). Pengembalian kereta kemudian akan ditampilkan sebagai CRkarakter.

  2. Lakukan Find & Replace ( Search> Replace...) dan centang Extended (\n, \r, \t, \0, \x...)opsi.

  3. Ketik \rdi Find what :bidang dan kosongkan Replace with :bidang tersebut sehingga tidak ada apa pun di dalamnya.

  4. Mulai dari bagian atas file, klik Replacetombol sampai semua karakter carriage return ( CR) telah dihapus dari bagian Linux atas. Pastikan untuk meninggalkan karakter carriage return ( CR) untuk bagian Windows.

Hasilnya adalah setiap perintah Linux diakhiri hanya dengan feed baris ( LF) dan setiap perintah Windows diakhiri dengan carriage return dan line feed ( CR LF).


1

Saya menggunakan teknik ini untuk membuat file jar yang dapat dijalankan. Karena file jar / zip dimulai di header zip, saya dapat meletakkan skrip universal untuk menjalankan file ini di bagian atas:

#!/usr/bin/env sh
@ 2>/dev/null # 2>nul & echo off
:; alias ::=''
:: exec java -jar $JAVA_OPTS "$0" "$@"
:: exit
java -jar %JAVA_OPTS% "%~dpnx0" %*
exit /B
  • Baris pertama tidak bergema di cmd dan tidak mencetak apa pun di sh. Ini karena @in sh melontarkan kesalahan yang disalurkan ke /dev/nulldan setelah itu komentar dimulai. Pada cmd pipa /dev/nullgagal karena file tidak dikenali di windows tetapi karena windows tidak mendeteksi #sebagai komentar kesalahan disalurkan ke nul. Kemudian ia menggema. Karena seluruh baris didahului oleh sebuah @itu tidak mendapatkan printet pada cmd.
  • Yang kedua mendefinisikan ::, yang memulai komentar di cmd, ke noop di sh. Ini memiliki manfaat yang ::tidak me-reset $?ke 0. Ini menggunakan :;trik " adalah label".
  • Sekarang saya dapat menambahkan perintah sh dengan ::dan mereka diabaikan dalam cmd
  • Pada :: exitskrip sh berakhir dan saya dapat menulis perintah cmd
  • Hanya baris pertama (shebang) yang bermasalah dalam cmd karena akan dicetak command not found. Anda harus memutuskan sendiri apakah Anda membutuhkannya atau tidak.

0

Saya membutuhkan ini untuk beberapa skrip instalasi paket Python saya. Sebagian besar hal antara file sh dan bat sama tetapi beberapa hal seperti penanganan kesalahan berbeda. Salah satu cara untuk melakukannya adalah sebagai berikut:

common.inc
----------
common statement1
common statement2

Kemudian Anda menyebutnya dari skrip bash:

linux.sh
--------
# do linux specific stuff
...
# call common code
source common.inc

File batch Windows terlihat seperti ini:

windows.bat
-----------
REM do windows specific things
...
# call common code
call common.inc


-6

Ada alat build independen platform seperti Ant atau Maven dengan sintaks xml (berdasarkan Java). Jadi, Anda bisa menulis ulang semua skrip Anda di Ant atau Maven dan menjalankannya meskipun tipe os. Atau Anda bisa membuat skrip Ant wrapper, yang akan menganalisis jenis os dan menjalankan skrip bat atau bash yang sesuai.


5
Sepertinya itu penyalahgunaan yang besar dari alat-alat itu. Jika Anda ingin menghindari pertanyaan sebenarnya (berjalan di shell asli) maka Anda harus menyarankan bahasa skrip universal, seperti python, bukan alat build yang kebetulan dapat disalahgunakan sebagai pengganti bahasa skrip yang tepat.
ArtOfWarfare
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.