要在Windows批处理和Linux Bash中运行的单个脚本?


96

是否可以编写在Windows(视为.bat)和Linux(通过Bash)中执行的单个脚本文件?

我知道两者的基本语法,但没有弄清楚。它可能利用了一些Bash晦涩的语法或Windows批处理处理器的故障。

要执行的命令可能只是执行其他脚本的一行。

这样做的动机是针对Windows和Linux仅具有一个应用程序启动命令。

更新:系统“本机” shell脚本的需要是它需要选择正确的解释器版本,符合某些知名的环境变量等。不建议安装其他环境,例如CygWin-我想保留“下载并运行”。

Windows唯一需要考虑的其他语言是Windows脚本宿主-WSH,自98起默认预设。


9
查看Perl或Python。这些是在两个平台上都可用的脚本语言。
a_horse_with_no_name

groovy,python,ruby有人吗? stackoverflow.com/questions/257730/...
Kalpesh瑞里

Groovy默认情况下可以在所有系统上都很棒,我将其作为Shell脚本语言。也许有一天:)
OndraŽižka'16


2
一个引人注目的用例是教程-能够告诉人们“运行这个小脚本”而不必解释“如果您使用的是Windows,请使用此小脚本,但是如果您使用的是Linux或Mac,请尝试以下操作”相反,由于我在Windows上,因此我尚未进行实际测试。” 遗憾的是,Windows没有cp内置的基本类Unix命令,反之亦然,因此编写两个单独的脚本在教学上可能比此处显示的高级技术更好。
Qwertie

Answers:


92

我所做的是使用cmd的标签语法作为注释标记。标签字符冒号(:true在大多数POSIXish外壳程序中均等效。如果您立即在标签字符后面加上另一个不能在中使用的字符GOTO,则注释cmd脚本不应影响您的cmd代码。

技巧是将代码行放在字符序列“ :;”之后。如果您主要是编写单行脚本,或者可以(sh对于许多情况)可以编写一行cmd,则可能会很好。不要忘记$?在下一个冒号之前必须使用,:因为它会:重置$?为0。

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

一个非常人为的守卫示例$?

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

跳过cmd代码的另一个想法是使用heredocs,以便shcmd代码视为未使用的字符串并对其进行cmd解释。在这种情况下,我们确保将Heredoc的定界符都加引号(使用来停止sh对其内容进行任何形式的解释sh)并以开头,:以便cmd像其他任何以开头的行一样跳过它:

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

根据您的需求或编码样式,隔行扫描cmdsh编码可能有意义,也可能没有意义。使用heredocs是执行这种隔行扫描的一种方法。但是,可以使用以下GOTO技术将其扩展:

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

当然,通用注释可以使用字符序列: #或来完成:;#。空格或分号是必需的,因为如果它不是标识符的第一个字符,则会被sh视为#命令名称的一部分。例如,您可能想在使用该GOTO方法拆分代码之前在文件的第一行中编写通用注释。然后,您可以告知读者您的脚本为何如此奇怪地编写:

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

因此,据我所知,完成shcmd兼容脚本的一些想法和方法没有严重的副作用(并且没有cmd输出'#' is not recognized as an internal or external command, operable program or batch file.)。


5
请注意,这需要脚本具有.cmd扩展名,否则它将无法在Windows中运行。有什么解决方法吗?
jviotti 2015年

1
如果要编写批处理文件,我建议仅命名它们.cmd并将其标记为可执行文件。这样,可以从Windows或UNIX上的默认外壳执行脚本(仅使用bash测试)。因为我想不出一种方法来包含shebang而不会大声抱怨cmd,所以必须始终通过shell调用脚本。即,一定要使用execlp()or execvp()(我认为system()这对您来说是“正确”的方式)而不是execl()or execv()(这些后一个功能仅在带有shebang的脚本上起作用,因为它们更直接地进入OS)。
Binki 2015年

我省略了有关文件扩展名的讨论,因为我在外壳程序中(例如<Exec/>MSBuild Task)而不是作为独立的批处理文件编写可移植代码。也许将来我有空的话,我会用这些评论中的信息来更新答案……
binki 2015年

23

您可以尝试以下方法:

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

可能需要用/r/n新行代替unix样式。如果我记得正确的话,unix新行不会被.bat脚本解释为新行。另一种方法是#.exe在路径中创建一个不执行任何操作的文件与我在此处的答案类似的方式: 是否可以在不使用临时文件的情况下在批处理文件中嵌入和执行VBScript?

编辑

binki的回答几乎是完美的,但仍然可以加以改进:

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

echo $PATH

它再次使用了:技巧和多行注释。看起来像cmd.exe(至少在Windows10上有效)在unix样式的EOL中没有问题,因此请确保将脚本转换为linux格式。(在这里这里之前,已经看到使用相同的方法)。虽然使用shebang仍然会产生多余的输出...


3
/r/n除非您在每行末尾添加一个#(即#/r/n),否则行末尾的bash脚本会引起很多麻烦-这样,\r它将被视为注释的一部分而被忽略。
Gordon Davisson

2
实际上,我发现它# 2>NUL & ECHO Welcome to %COMSPEC%.可以隐藏关于#找不到命令的错误cmd。当您需要编写一条仅由cmd(例如Makefile)执行的独立行时,此技术很有用。
2014年

11

我想发表评论,但目前只能添加一个答案。

给出的技术非常好,我也使用它们。

很难保留其中包含两种换行符的文件,即/n用于bash部分和/r/n用于Windows部分的换行符。大多数编辑器通过猜测您正在编辑哪种文件来尝试实施通用的换行方案。同样,大多数在Internet上传输文件的方法(尤其是文本或脚本文件)都将洗涤换行符,因此您可以从一种换行符开始,到另一种换行符结束。如果您对换行进行了假设,然后将脚本交给其他人使用,他们可能会发现该脚本不适用于他们。

另一个问题是网络安装的文件系统(或CD),它们在不同的系统类型之间共享(特别是在您无法控制用户可用的软件的情况下)。

因此,应该使用DOS的换行符,/r/n/r通过在每行的末尾添加一个注释来保护bash脚本免受DOS 的侵害#。您也不能在bash中使用行连续,因为/r会导致行中断。

这样,无论在任何环境中使用脚本的人都可以使用它。

我将这种方法与制作便携式Makefile结合使用!


6

与上述答案不同,以下内容对我有用,在Bash 4和Windows 10中没有任何错误或错误消息。我将文件命名为“ whatever.cmd”,chmod +x使其在linux中可执行,并使其具有unix行尾(dos2unix),以使bash保持安静。

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

您可以共享变量:

:;SET() { eval $1; }

SET var=value

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

2

有几种方法可以在同一脚本上bash以及cmd在同一脚本上执行不同的命令。

cmd将忽略:;其他以开头的以开头的行。如果当前行以命令结尾,它将也忽略下一行rem ^,因为^字符将逃脱换行符,并且下一行将被视为注释rem

至于bash忽略这些cmd行,有多种方法。我列举了一些不破坏 cmd命令的方法:

不存在的#命令(不建议)

如果在运行脚本时没有#可用的命令cmd,我们可以这样做:

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

该行#开头的字符cmd使bash该行作为注释。

正如布莱恩·汤普塞特(Brian Tompsett)在回答中指出的那样#,该bash行末尾的字符用于注释掉该\r字符。如果没有该文件,则如果文件具有行尾(由要求),将引发错误。bash\r\ncmd

通过这样做# 2>nul,我们欺骗cmd了一些不存在的#命令的错误,同时仍然执行后面的命令。

如果上有#可用命令,PATH或者您无法控制可用的命令,请不要使用此解决方案cmd


使用echo忽略#人品cmd

我们可以将echo其输出重定向使用,以cmdbash的注释区域插入命令:

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

由于该#字符没有特殊含义cmd,因此将其视为to的一部分echo。我们要做的就是重定向echo命令的输出,并在其后插入其他命令。


#.bat文件

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

echo >/dev/null # 1>nul 2> #.bat行在#.bat打开时会创建一个空文件cmd(或替换现有文件#.bat,如果有的话),而在打开时则不执行任何操作bash

cmd即使。上还有其他#命令,此文件也将由其后的行使用PATH

del #.bat该命令cmd特异性代码删除已创建的文件。您只需要在最后cmd一行上执行此操作。

如果#.bat文件可能位于您当前的工作目录中,请不要使用此解决方案,因为该文件将被删除。


推荐:使用here-document忽略cmd命令bash

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

通过将^字符放在行的末尾,cmd我们可以使换行符转义,并:用作此处的定界符,定界符的行内容将对无效cmd。这样,cmd将仅在行结束后执行其行:,其行为与相同bash

如果您想在两个平台上都有多行并且仅在代码块的末尾执行,则可以执行以下操作:

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

只要cmd与完全不符here-document delimiter,该解决方案就可以工作。您可以更改here-document delimiter为任何其他文本。


在所有提出的解决方案中,命令仅在最后一行之后执行,如果它们在两个平台上执行相同的操作,则使它们的行为保持一致。

这些解决方案必须以\r\n换行符保存到文件中,否则它们将无法使用cmd


迟到总比没有好...这比其他答案更能帮助我。谢谢!!
迪迪

2

先前的答案似乎涵盖了几乎所有选项,并为我提供了很多帮助。我在此处包含此答案只是为了演示用于在同一文件中同时包含Bash脚本和Windows CMD脚本的机制。

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.

摘要

在Linux中

第一行(echo >/dev/null # >nul & GOTO WINDOWS & rem ^)将被忽略,脚本将在紧随其后的每一行中流动,直到exit 0执行命令为止。一旦exit 0到达,脚本执行将结束,并忽略其下面的Windows命令。

在Windows中

第一行将执行 GOTO WINDOWS命令,跳过紧随其后的Linux命令,并继续在该:WINDOWS行执行。

在Windows中删除回车

由于我是在Windows中编辑此文件的,因此我必须系统地从Linux命令中删除回车符(\ r),否则在运行Bash部分时会得到异常结果。为此,我在Notepad ++中打开文件并执行以下操作:

  1. 打开该选项用于观看的行的字符结束时(View> Show Symbol> Show End of Line)。然后,回车将显示为CR字符。

  2. 查找并替换(Search> Replace...)并检查Extended (\n, \r, \t, \0, \x...)选项。

  3. 类型\rFind what :字段,空出的Replace with :字段,以便有什么也没有。

  4. 从文件的顶部开始,单击Replace按钮,直到CR从顶部Linux部分删除了所有回车符()。确保CR在Windows部分保留回车符()。

结果应该是,每个Linux命令仅以换行符(LF)结尾,并且每个Windows命令以回车符和换行符(CR LF)结尾。


1

我使用这种技术来创建可运行的jar文件。由于jar / zip文件从zip标头开始,因此我可以在该文件的顶部放置一个通用脚本来运行该文件:

#!/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
  • 第一行确实在cmd中回显,并且在sh上不打印任何内容。这是因为@in sh引发了一个错误,该错误通过管道传递/dev/null给注释,然后注释开始。在cmd上,/dev/null由于无法在Windows上识别该文件,但由于Windows不能将其检测#为注释,因此该管道连接失败nul。然后它会产生回音。因为整行前面都有一个@开头,所以不会在cmd上显示printet。
  • 第二个定义::,以cmd开头的注释,以sh开头。这样做的好处是::不会重置$?0。它使用“:;是标签”技巧。
  • 现在我可以在前面加上sh命令 ::它们在cmd中将被忽略
  • :: exit sh脚本结束时,我可以编写cmd命令
  • cmd中只有第一行(shebang)存在问题,因为它将打印command not found。您必须自行决定是否需要它。

0

我的某些Python软件包安装脚本需要此文件。sh和bat文件之间的大多数情况相同,但错误处理之类的东西很少。一种方法如下:

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

然后从bash脚本调用此命令:

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

Windows批处理文件如下所示:

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


-6

有一个与平台无关的构建工具,例如Ant或Maven,具有xml语法(基于Java)。因此,您可以在Ant或Maven中重写所有脚本,然后尽管使用os类型也可以运行它们。或者,您可以只创建Ant包装程序脚本,该脚本将分析os类型并运行适当的bat或bash脚本。


5
这似乎是对这些工具的严重滥用。如果要避免实际问题(在本机外壳中运行),则应建议使用通用脚本语言(例如python),而不是一种恰巧能够被用作适当脚本语言替代品的构建工具。
ArtOfWarfare 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.