Tập lệnh đơn để chạy trong cả Windows batch và Linux Bash?


96

Có thể viết một tệp kịch bản duy nhất thực thi trong cả Windows (được coi là .bat) và Linux (thông qua Bash) không?

Tôi biết cú pháp cơ bản của cả hai, nhưng không tìm ra. Nó có thể khai thác một số cú pháp khó hiểu của Bash hoặc một số trục trặc trong bộ xử lý hàng loạt của Windows.

Lệnh để thực thi có thể chỉ là một dòng duy nhất để thực thi tập lệnh khác.

Động lực là chỉ có một lệnh khởi động ứng dụng duy nhất cho cả Windows và Linux.

Cập nhật: Nhu cầu đối với tập lệnh shell "gốc" của hệ thống là nó cần chọn phiên bản thông dịch phù hợp, tuân theo một số biến môi trường nổi tiếng nhất định, v.v. Cài đặt các môi trường bổ sung như CygWin là không thích hợp - Tôi muốn giữ nguyên khái niệm " tải xuống và chạy ".

Ngôn ngữ khác duy nhất cần xem xét cho Windows là Windows Scripting Host - WSH, được đặt trước theo mặc định từ năm 98.


9
Nhìn vào Perl hoặc Python. Đó là những ngôn ngữ kịch bản có sẵn trên cả hai nền tảng.
a_horse_with_no_name

Groovy, python, ruby ​​ai? stackoverflow.com/questions/257730/…
Kalpesh Soni,

Groovy sẽ rất tuyệt nếu có trên tất cả các hệ thống theo mặc định, tôi sẽ coi nó như một ngôn ngữ kịch bản shell. Có lẽ một ngày nào đó :)
Ondra Žižka


2
Một trường hợp sử dụng hấp dẫn cho điều này là các hướng dẫn - để có thể nói với mọi người "chạy tập lệnh nhỏ này" mà không cần phải giải thích rằng "nếu bạn đang sử dụng Windows, hãy sử dụng tập lệnh nhỏ này, nhưng nếu bạn đang sử dụng Linux hoặc Mac, hãy thử điều này thay vào đó, tôi chưa thực sự thử nghiệm vì tôi đang sử dụng Windows. " Đáng buồn thay, Windows không có các lệnh giống unix cơ bản như cpđược tích hợp sẵn hoặc ngược lại, vì vậy việc viết hai tập lệnh riêng biệt có thể tốt hơn về mặt sư phạm so với các kỹ thuật nâng cao được hiển thị ở đây.
Qwertie,

Câu trả lời:


92

Những gì tôi đã làm là sử dụng cú pháp nhãn của cmd làm điểm đánh dấu nhận xét . Ký tự nhãn, dấu hai chấm ( :), tương đương với truetrong hầu hết các trình bao POSIXish. Nếu bạn ngay lập tức theo dõi ký tự nhãn bởi một ký tự khác không thể được sử dụng trong a GOTO, thì việc nhận xét cmdtập lệnh của bạn sẽ không ảnh hưởng đến cmdmã của bạn .

Cách hack là đặt các dòng mã sau chuỗi ký tự “ :;”. Nếu bạn chủ yếu đang viết các tập lệnh một dòng hoặc, tùy trường hợp, có thể viết một dòng shcho nhiều dòng cmd, thì cách sau có thể ổn. Đừng quên rằng bất kỳ việc sử dụng nào $?cũng phải ở trước dấu hai chấm tiếp theo của bạn ::đặt lại $?thành 0.

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

Một ví dụ rất điển hình về việc canh gác $?:

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

Một ý tưởng khác để bỏ qua cmdmã là sử dụng heredocs để shxử lý cmdmã như một chuỗi không sử dụng và cmddiễn giải nó. Trong trường hợp này, chúng tôi đảm bảo rằng dấu phân tách heredoc của chúng tôi đều được trích dẫn (để dừng shthực hiện bất kỳ loại diễn giải nào đối với nội dung của nó khi chạy với sh) và bắt đầu bằng :để cmdbỏ qua nó giống như bất kỳ dòng nào khác bắt đầu bằng :.

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

Tùy thuộc vào nhu cầu hoặc phong cách mã hóa của bạn, việc xen kẽ cmdshmã có thể có ý nghĩa hoặc không. Sử dụng heredocs là một phương pháp để thực hiện xen kẽ như vậy. Tuy nhiên, điều này có thể được mở rộng với GOTOkỹ thuật :

:<<"::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%

Tất nhiên, nhận xét phổ biến có thể được thực hiện với chuỗi ký tự : #hoặc :;#. Khoảng trắng hoặc dấu chấm phẩy là cần thiết vì shđược coi #là một phần của tên lệnh nếu nó không phải là ký tự đầu tiên của mã định danh. Ví dụ: bạn có thể muốn viết các nhận xét chung trong các dòng đầu tiên của tệp trước khi sử dụng GOTOphương pháp để tách mã của bạn. Sau đó, bạn có thể thông báo cho người đọc về lý do tại sao kịch bản của bạn được viết rất kỳ lạ:

: # 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%

Vì vậy, một số ý tưởng và cách để hoàn thành shcmdcác tập lệnh tương thích mà không có tác dụng phụ nghiêm trọng theo như tôi biết (và không có cmdđầu ra '#' is not recognized as an internal or external command, operable program or batch file.).


5
Lưu ý rằng điều này yêu cầu tập lệnh phải có .cmdphần mở rộng, nếu không nó sẽ không chạy trong Windows. Có bất kỳ công việc xung quanh?
jviotti

1
Nếu bạn đang viết các tệp hàng loạt, tôi khuyên bạn chỉ nên đặt tên cho chúng .cmdvà đánh dấu chúng là có thể thực thi được. Bằng cách đó, việc thực thi tập lệnh từ trình bao mặc định trên Windows hoặc unix sẽ hoạt động (chỉ được thử nghiệm với bash). Vì tôi không thể nghĩ ra cách để bao gồm một shebang mà không khiến cmd phàn nàn lớn, bạn phải luôn gọi script qua shell. Tức là, hãy chắc chắn sử dụng execlp()hoặc execvp()(tôi nghĩ system()đây là cách “phù hợp” với bạn) hơn là execl()hoặc execv()(các chức năng sau này sẽ chỉ hoạt động trên các tập lệnh có shebang vì chúng đi trực tiếp hơn đến hệ điều hành).
binki

Tôi đã bỏ qua thảo luận về phần mở rộng tệp vì tôi đang viết mã di động của mình trong trình bao (ví dụ: <Exec/>Nhiệm vụ MSBuild ) thay vì dưới dạng tệp hàng loạt độc lập. Có lẽ nếu có thời gian trong tương lai, tôi sẽ cập nhật câu trả lời bằng thông tin trong các bình luận này…
binki

23

bạn có thể thử điều này:

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

Có lẽ bạn sẽ cần phải sử dụng /r/nnhư một dòng mới thay vì một kiểu unix. Nếu tôi nhớ chính xác thì dòng mới của unix không được hiểu là một dòng mới theo .batscript. Một cách khác là tạo một #.exetệp trong đường dẫn mà không làm gì trong theo cách tương tự như câu trả lời của tôi ở đây: Có thể nhúng và thực thi VBScript trong một tệp loạt mà không sử dụng tệp tạm thời không?

BIÊN TẬP

Các câu trả lời của binki là gần như hoàn hảo nhưng vẫn có thể được cải thiện:

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

echo $PATH

Nó sử dụng lại :thủ thuật và nhận xét nhiều dòng. Có vẻ như cmd.exe (ít nhất là trên windows10) hoạt động mà không gặp vấn đề với các EOL kiểu unix, vì vậy hãy đảm bảo rằng tập lệnh của bạn được chuyển đổi thành định dạng linux. (phương pháp tương tự đã được sử dụng trước đâyở đây ). Mặc dù sử dụng shebang vẫn sẽ tạo ra sản lượng dư thừa ...


3
/r/nở cuối dòng sẽ khiến nhiều người đau đầu trong tập lệnh bash trừ khi bạn kết thúc mỗi dòng bằng #(tức là #/r/n) - theo cách đó, dấu \rsẽ được coi là một phần của nhận xét và bị bỏ qua.
Gordon Davisson,

2
Trên thực tế, tôi thấy rằng # 2>NUL & ECHO Welcome to %COMSPEC%.quản lý để ẩn lỗi về #lệnh không được tìm thấy bởi cmd. Kỹ thuật này hữu ích khi bạn cần viết một dòng độc lập sẽ chỉ được thực thi bởi cmd(ví dụ: trong a Makefile).
binki

11

Tôi muốn bình luận, nhưng chỉ có thể thêm câu trả lời vào lúc này.

Các kỹ thuật được đưa ra là tuyệt vời và tôi cũng sử dụng chúng.

Thật khó để giữ lại một tệp có hai loại ngắt dòng bên trong nó, đó là /ncho phần bash và /r/ncho phần cửa sổ. Hầu hết các trình chỉnh sửa đều thử và thực thi một sơ đồ ngắt dòng chung bằng cách đoán loại tệp bạn đang chỉnh sửa. Ngoài ra, hầu hết các phương pháp truyền tệp qua internet (đặc biệt là tệp văn bản hoặc tập lệnh) sẽ rửa các ngắt dòng, vì vậy bạn có thể bắt đầu bằng một loại ngắt dòng và kết thúc bằng loại khác. Nếu bạn đưa ra các giả định về ngắt dòng và sau đó đưa tập lệnh của mình cho người khác sử dụng, họ có thể thấy nó không phù hợp với họ.

Vấn đề khác là hệ thống tệp được gắn trên mạng (hoặc đĩa CD) được chia sẻ giữa các loại hệ thống khác nhau (đặc biệt khi bạn không thể kiểm soát phần mềm có sẵn cho người dùng).

Do đó, người ta nên sử dụng ngắt dòng của DOS /r/nvà cũng bảo vệ tập lệnh bash khỏi DOS /rbằng cách đặt một chú thích ở cuối mỗi dòng ( #). Bạn cũng không thể sử dụng các dòng liên tục trong bash vì điều /rnày sẽ khiến chúng bị đứt đoạn.

Bằng cách này, bất cứ ai sử dụng script và trong bất kỳ môi trường nào, nó sẽ hoạt động.

Tôi sử dụng phương pháp này kết hợp với tạo Makefiles di động!


6

Phần sau hoạt động với tôi mà không có bất kỳ lỗi hoặc thông báo lỗi nào với Bash 4 và Windows 10, không giống như các câu trả lời ở trên. Tôi đặt tên tệp là "anything.cmd", làm chmod +xđể nó thực thi được trong linux và làm cho nó có đuôi dòng unix ( dos2unix) để giữ im lặng.

:; 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

Bạn có thể chia sẻ các biến:

:;SET() { eval $1; }

SET var=value

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

2

Có một số cách thực hiện các lệnh khác nhau trên bashcmdcùng một tập lệnh.

cmdsẽ bỏ qua các dòng bắt đầu bằng :;, như đã đề cập trong các câu trả lời khác. Nó cũng sẽ bỏ qua dòng tiếp theo nếu dòng hiện tại kết thúc bằng lệnh rem ^, vì ^ký tự sẽ thoát khỏi ngắt dòng và dòng tiếp theo sẽ được coi là nhận xét bởirem .

Đối với việc bashbỏ qua các cmddòng, có nhiều cách. Tôi đã liệt kê một số cách để làm điều đó mà không vi phạm các cmdlệnh:

#Lệnh không tồn tại (không được khuyến nghị)

Nếu không có #lệnh nào khả dụng cmdkhi chạy tập lệnh, chúng ta có thể thực hiện việc này:

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

#tự ở đầu cmddòng bashcoi dòng đó như một nhận xét.

#tự ở cuối bashdòng được dùng để nhận xét về \rnhân vật, như Brian Tompsett đã chỉ ra trong câu trả lời của mình . Nếu không có điều này, bashsẽ gây ra lỗi nếu tệp có \r\nphần cuối dòng, được yêu cầu bởi cmd.

Bằng cách đó # 2>nul, chúng tôi đang lừa cmdđể bỏ qua lỗi của một số #lệnh không tồn tại , trong khi vẫn thực hiện lệnh sau đó.

Không sử dụng giải pháp này nếu có một #lệnh có sẵn trên PATHhoặc nếu bạn không có quyền kiểm soát các lệnh có sẵn cmd.


Sử dụng echođể bỏ qua #ký tự trêncmd

Chúng tôi có thể sử dụng echovới đầu ra của nó được chuyển hướng để chèn cmdcác lệnh vào bashkhu vực đã nhận xét của:

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

#ký tự không có ý nghĩa đặc biệt trên cmd, nó được coi là một phần của văn bản echo. Tất cả những gì chúng ta phải làm là chuyển hướng đầu ra của echolệnh và chèn các lệnh khác sau nó.


#.batTệp trống

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

Các echo >/dev/null # 1>nul 2> #.batdòng tạo ra một sản phẩm nào #.battập tin trong khi trên cmd(hoặc thay thế hiện có #.bat, nếu có), và không có gì trong khi trên bash.

Tệp này sẽ được sử dụng bởi cmd(các) dòng theo sau ngay cả khi có một số #lệnh khác trên PATH.

Các del #.batlệnh trên cmdđang cụ thể xóa các tập tin đã được tạo ra. Bạn chỉ phải làm điều này ở cmddòng cuối cùng .

Không sử dụng giải pháp này nếu #.battệp có thể nằm trong thư mục làm việc hiện tại của bạn, vì tệp đó sẽ bị xóa.


Khuyến nghị: sử dụng here-document để bỏ qua cmdcác lệnh trênbash

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

Bằng cách đặt ^ký tự ở cuối cmddòng, chúng ta đang thoát khỏi dấu ngắt dòng và bằng cách sử dụng :làm dấu phân cách ở đây tài liệu, nội dung dòng phân cách sẽ không có tác dụng cmd. Bằng cách đó, cmdsẽ chỉ thực hiện dòng của nó sau khi :dòng kết thúc, có cùng hành vi như bash.

Nếu bạn muốn có nhiều dòng trên cả hai nền tảng và chỉ thực thi chúng ở cuối khối, bạn có thể thực hiện điều này:

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

Miễn là không có cmddòng chính xác here-document delimiter, giải pháp này sẽ hoạt động. Bạn có thể thay đổi here-document delimiterthành bất kỳ văn bản nào khác.


Trong tất cả các giải pháp đã trình bày, các lệnh sẽ chỉ được thực thi sau dòng cuối cùng , làm cho hành vi của chúng nhất quán nếu chúng thực hiện cùng một điều trên cả hai nền tảng.

Các giải pháp đó phải được lưu vào tệp có dấu \r\nngắt dòng, nếu không chúng sẽ không hoạt động cmd.


Muộn còn hơn không ... điều này giúp tôi nhiều hơn những câu trả lời khác. Cảm ơn!!
A-Diddy

2

Các câu trả lời trước dường như bao gồm khá nhiều lựa chọn và đã giúp tôi rất nhiều. Tôi bao gồm câu trả lời này ở đây chỉ để chứng minh cơ chế tôi đã sử dụng để bao gồm cả tập lệnh Bash và tập lệnh Windows CMD trong cùng một tệp.

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.

Tóm lược

Trong Linux

Dòng đầu tiên ( echo >/dev/null # >nul & GOTO WINDOWS & rem ^) sẽ bị bỏ qua và tập lệnh sẽ chảy qua từng dòng ngay sau đó cho đến khi exit 0lệnh được thực thi. Sau khi exit 0đạt được, quá trình thực thi tập lệnh sẽ kết thúc, bỏ qua các lệnh Windows bên dưới nó.

Trong Windows

Dòng đầu tiên sẽ thực thi GOTO WINDOWSlệnh, bỏ qua các lệnh Linux ngay sau đó và tiếp tục thực hiện tại :WINDOWSdòng.

Xóa trả hàng vận chuyển trong Windows

Vì tôi đang chỉnh sửa tệp này trong Windows, nên tôi phải loại bỏ các ký tự xuống dòng (\ r) khỏi các lệnh Linux một cách có hệ thống, nếu không tôi nhận được kết quả bất thường khi chạy phần Bash. Để thực hiện việc này, tôi đã mở tệp trong Notepad ++ và thực hiện như sau:

  1. Bật tùy chọn để xem kết thúc ký tự dòng ( View> Show Symbol> Show End of Line). Khi đó, kết quả vận chuyển sẽ hiển thị dưới dạng CRký tự.

  2. Thực hiện Tìm & Thay thế ( Search> Replace...) và Extended (\n, \r, \t, \0, \x...)chọn tùy chọn.

  3. Nhập \rvào Find what :trường và bỏ trống Replace with :trường để không có gì trong đó.

  4. Bắt đầu từ đầu tệp, nhấp vào Replacenút cho đến khi tất cả các CRký tự xuống dòng ( ) được xóa khỏi phần Linux trên cùng. Đảm bảo để lại các CRký tự xuống dòng ( ) cho phần Windows.

Kết quả là mỗi lệnh Linux chỉ kết thúc bằng một nguồn cấp dòng ( LF) và mỗi lệnh Windows kết thúc bằng một ký tự xuống dòng và nguồn cấp dòng ( CR LF).


1

Tôi sử dụng kỹ thuật này để tạo các tệp jar có thể chạy được. Vì tệp jar / zip bắt đầu ở tiêu đề zip, tôi có thể đặt một tập lệnh phổ quát để chạy tệp này ở trên cùng:

#!/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
  • Dòng đầu tiên hiện ra tiếng vọng trong cmd và không in bất cứ thứ gì trên sh. Điều này là do @in sh ném ra một lỗi được chuyển đến /dev/nullvà sau đó một nhận xét bắt đầu. Trên cmd đường ống /dev/nullkhông thành công vì tệp không được nhận dạng trên cửa sổ nhưng vì cửa sổ không phát hiện #dưới dạng nhận xét nên lỗi được gửi đến nul. Sau đó, nó tắt tiếng vọng. Bởi vì toàn bộ dòng được đặt trước bởi một @nó không nhận được printet trên cmd.
  • Định nghĩa thứ hai ::, bắt đầu một bình luận trong cmd, noop trong sh. Điều này có lợi mà ::không thiết lập lại $?để 0. Nó sử dụng thủ thuật " :;là một nhãn".
  • Bây giờ tôi có thể thêm trước các lệnh sh ::và chúng bị bỏ qua trong cmd
  • Trên :: exittập lệnh sh kết thúc và tôi có thể viết lệnh cmd
  • Chỉ dòng đầu tiên (shebang) là có vấn đề trong cmd vì nó sẽ in command not found. Bạn phải tự quyết định xem bạn có cần nó hay không.

0

Tôi cần điều này cho một số tập lệnh cài đặt gói Python của mình. Hầu hết mọi thứ giữa tệp sh và bat đều giống nhau nhưng một số thứ như xử lý lỗi là khác nhau. Một cách để thực hiện việc này như sau:

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

Sau đó, bạn gọi điều này từ tập lệnh bash:

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

Tệp lô của Windows trông giống như sau:

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


-6

Có một công cụ xây dựng độc lập nền tảng như Ant hoặc Maven với cú pháp xml (dựa trên Java). Vì vậy, bạn có thể viết lại tất cả các tập lệnh của mình trong Ant hoặc Maven và chạy chúng bất chấp loại hệ điều hành. Hoặc bạn có thể chỉ cần tạo tập lệnh Ant wrapper, tập lệnh này sẽ phân tích loại hệ điều hành và chạy tập lệnh bat hoặc bash thích hợp.


5
Đó dường như là một sự lạm dụng hoàn toàn các công cụ đó. Nếu bạn muốn tránh câu hỏi thực tế (chạy trong native shell) thì bạn nên đề xuất một ngôn ngữ kịch bản phổ biến, như python, không phải là một công cụ xây dựng có khả năng bị sử dụng sai để thay thế cho một ngôn ngữ kịch bản thích hợp.
ArtOfWarfare
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.