单个脚本可以在Windows批处理和Linux Bash中运行吗?

时间:2013-07-07 09:05:49

标签: bash batch-file

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

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

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

动机是只为Windows和Linux提供一个应用程序启动命令。

更新:对系统“本机”shell脚本的需求是它需要选择正确的解释器版本,符合某些众所周知的环境变量等。安装CygWin等其他环境不是首选 - 我想保持“下载和运行”的概念。

Windows要考虑的唯一其他语言是Windows Scripting Host - WSH,默认情况下是自98以来预设的。

11 个答案:

答案 0 :(得分:74)

我所做的是use cmd’s label syntax as comment marker。标签字符冒号(:)在大多数POSIXish shell中等同于true。如果您立即按照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 technique

进行扩展
:<<"::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 https://stackoverflow.com/questions/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.

答案 1 :(得分:19)

你可以试试这个:

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

可能你需要使用/r/n作为新行而不是unix样式。如果我记得正确的话,unix新行不会被.bat脚本解释为新行。另一种方法是在路径中创建一个#.exe文件,它不会像我在这里的回答那样做任何事情:Is it possible to embed and execute VBScript within a batch file without using a temporary file?

修改

binki's answer几乎是完美的,但仍然可以改进:

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

echo $PATH

它再次使用了:技巧和多行注释。像cmd.exe(至少在windows10上)这样的工作没有unix样式EOL的问题,所以请确保你的脚本转换成linux格式。 (在herehere之前已经看到过相同的方法)。虽然使用shebang仍会产生冗余输出......

答案 2 :(得分:11)

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

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

很难保留一个包含两种换行符的文件,bash部分为/n,windows部分为/r/n。大多数编辑通过猜测您正在编辑的文件类型来尝试并执行公共换行符方案。此外,大多数通过互联网传输文件的方法(特别是文本或脚本文件)将清除换行符,因此您可以从一种换行符开始,最后用另一种换行符。如果您对换行符做出了假设,然后将您的脚本提供给其他人使用,他们可能会发现它对他们不起作用。

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

因此,应该使用/r/n的DOS换行符,并通过在每行(/r)的末尾添加注释来保护bash脚本免受DOS #的攻击。你也不能在bash中使用续行,因为/r会导致它们中断。

通过这种方式,无论谁使用该脚本,无论在何种环境中,它都将起作用。

我将此方法与制作便携式Makefile一起使用!

答案 3 :(得分:6)

与上面的答案不同,以下内容适用于我,没有任何错误或错误消息与Bash 4和Windows 10。我将文件命名为&#34; whatever.cmd&#34;,执行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

答案 4 :(得分:2)

使用相同的脚本在bashcmd上执行不同命令有多种方法。

cmd将忽略以:;开头的行,如其他答案中所述。如果当前行以命令rem ^结束,它也将忽略下一行,因为^字符将转义换行符,下一行将被rem视为注释

至于使bash忽略cmd行,有多种方法。我在不违反cmd命令的情况下列举了一些方法:

不存在#命令(不推荐)

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

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

#行开头的cmd字符会使bash将该行视为评论。

#行末尾的bash字符用于注释掉\r字符,就像Brian Tompsett在his answer中指出的那样。如果没有这个,bash如果文件\r\n需要cmd行结尾,则会抛出错误。

通过执行# 2>nul,我们会欺骗cmd忽略某些不存在的#命令的错误,同时仍然执行后面的命令。

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

<小时/>

使用echo忽略#

上的cmd字符

我们可以使用echo将其输出重定向,以在cmd注释掉的区域中插入bash个命令:

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

由于#字符在cmd上没有特殊含义,因此它被视为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行的末尾,我们将转义换行符,并使用:作为here-document分隔符,分隔符行内容对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更改为任何其他文字。

<小时/>

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

这些解决方案必须保存到\r\n作为换行符的文件中,否则他们将无法使用cmd < / p>

答案 5 :(得分:1)

您可以共享变量:

:;SET() { eval $1; }

SET var=value

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

答案 6 :(得分:1)

之前的答案似乎涵盖了几乎所有的选项并帮助了我很多。我在这里包含这个答案只是为了演示我曾经在同一个文件中包含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&gt; Show Symbol&gt; Show End of Line)。然后,回车符将显示为CR个字符。

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

  3. \r字段中输入Find what :并删除Replace with :字段,以便其中没有任何内容。

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

  5. 结果应该是每个Linux命令只以换行符(LF)结束,并且每个Windows命令都以回车符和换行符(CR LF)结束。 / p>

答案 7 :(得分:0)

我使用这种技术来创建可运行的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上打印任何内容。这是因为sh中的@会抛出一个传递给/dev/null的错误,然后评论开始。在cmd上,到/dev/null的管道失败,因为在Windows上无法识别该文件,但由于Windows未检测到#作为注释,因此错误通过管道传输到nul。然后它做了一个回声。因为整行前面有一个@,所以它不会在cmd上获得打印。
  • 第二个定义::,它在cmd中开始注释,在sh中定义为noop。这样做的好处是::不会将$?重置为0。它使用“:;是一个标签”技巧。
  • 现在我可以在::前加上sh命令,并在cmd
  • 中忽略它们
  • :: exit上,sh脚本结束,我可以编写cmd命令
  • 只有第一行(shebang)在cmd中存在问题,因为它会打印command not found。 如果你需要,你必须自己决定。

答案 8 :(得分:0)

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

filter(breads,unhealthy_breads,Healthy).

然后您从bash脚本中调用它:

Healthy = [italian_wheat, multigrain]

Windows批处理文件如下所示:

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

答案 9 :(得分:0)

https://github.com/skanga/bashwin尝试我的BashWin项目,该项目将BusyBox用于大多数Unix命令

答案 10 :(得分:-6)

有一个独立于平台的构建工具,如Ant或Maven,带有xml语法(基于Java)。 所以,你可以重写Ant或Maven中的所有脚本,尽管os类型也可以运行它们。 或者你可以创建Ant包装器脚本,它将分析os类型并运行适当的bat或bash脚本。