109

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

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

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

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

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

对于 Windows,唯一需要考虑的其他语言是 Windows Scripting Host - WSH,它是自 98 以来默认预设的。

4

11 回答 11

106

我所做的是使用 cmd 的标签语法作为注释标记。标签字符,一个冒号 ( :),true在大多数 POSIXish shell 中是等价的。如果您在标签字符后面紧跟另一个不能在 a 中使用的字符GOTO,那么注释您的cmd脚本不会影响您的cmd代码。

hack 是将代码行放在字符序列“<code>:;”之后。如果您主要编写单行脚本,或者(可能是这种情况)可以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 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.)。

于 2013-07-12T20:44:16.743 回答
27

编辑

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

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

echo $PATH

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


你可以试试这个:

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

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

于 2013-07-07T09:29:12.117 回答
12

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

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

很难保留其中包含两种换行符的文件,即/nbash 部分和/r/nwindows 部分。大多数编辑器通过猜测您正在编辑的文件类型来尝试执行通用换行方案。此外,大多数通过 Internet 传输文件的方法(特别是作为文本或脚本文件)都会清洗换行符,因此您可以从一种换行符开始并以另一种换行符结束。如果您对换行做出假设,然后将您的脚本交给其他人使用,他们可能会发现它对他们不起作用。

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

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

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

我将此方法与制作可移植的 Makefile 一起使用!

于 2014-12-17T13:58:00.427 回答
7

与上面的答案不同,以下内容对我有用,而 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
于 2016-08-19T04:24:41.890 回答
6

您可以共享变量:

:;SET() { eval $1; }

SET var=value

:;echo $var
:;exit
ECHO %var%
于 2017-10-04T12:44:34.967 回答
4

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

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

至于bash忽略cmd线条,有多种方法。我列举了一些在不破坏 cmd命令的情况下做到这一点的方法:

不存在的#命令(不推荐)

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

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

行首的#字符cmd使bash该行视为注释。

正如 Brian Tompsett 在他的回答中指出的那样,#该行末尾的字符用于bash注释该字符。没有这个,如果文件有.\rbash\r\ncmd

通过这样做# 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行在 on 时创建一个空#.bat文件cmd(或替换现有的#.bat,如果有的话),并且在 on 时不执行任何操作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为任何其他文本。


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

这些解决方案必须保存到带有\r\n换行符的文件中,否则它们将无法在cmd.

于 2017-07-27T03:18:39.283 回答
4

以前的答案似乎涵盖了几乎所有的选项,并帮助了我很多。我在这里包含这个答案只是为了演示我用来在同一个文件中包含 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. \r在该字段中键入Find what :并清空该Replace with :字段,以便其中没有任何内容。

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

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

于 2018-02-22T22:46:08.570 回答
3

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

#!/usr/bin/env sh\n
@ 2>/dev/null # 2>nul & echo off & goto BOF\r\n
:\n
<shell commands go here with \n line endings>
exit\n
\r\n
:BOF\r\n
<cmd commands go here with \r\n line endings>\r\n
exit /B %errorlevel%\r\n
}

如上所述设置行尾很重要,因为它们可能会在不同平台上引起问题。此外,如果跳转标签周围缺少正确的行尾,则 goto 语句在某些情况下将无法正常工作。

上面的技术是我目前使用的。下面是一个过时的版本,有深入的解释:

#!/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 中为 noop。这样做的好处是::不会重置$?0。它使用“:;是一个标签”的技巧。
  • 现在我可以在 sh 命令前面加上::它们,它们在 cmd 中被忽略
  • :: exitsh 脚本结束时,我可以编写 cmd 命令
  • 只有第一行(shebang)在 cmd 中有问题,因为它会打印command not found. 您必须自己决定是否需要。
于 2018-02-23T08:00:04.417 回答
1

我的一些 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
于 2019-10-31T04:38:22.270 回答
0

在https://github.com/skanga/bashwin尝试我的 BashWin 项目,它使用 BusyBox 处理大多数 Unix 命令

于 2020-04-23T17:20:58.193 回答
-6

有一个独立于平台的构建工具,如 Ant 或 Maven,具有 xml 语法(基于 Java)。因此,您可以在 Ant 或 Maven 中重写所有脚本并运行它们,而不管操作系统类型如何。或者您可以只创建 Ant 包装脚本,它将分析操作系统类型并运行适当的 bat 或 bash 脚本。

于 2013-07-07T09:10:10.940 回答