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

Ond*_*žka 87 bash batch-file

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

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

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

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

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

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

bin*_*nki 78

我所做的是使用cmd的标签语法作为注释标记.标签字符冒号(:)与true大多数POSIXish shell相同.如果您立即使用另一个不能在a中使用的字符跟随标签字符GOTO,那么注释您的cmd脚本不应该影响您的cmd代码.

黑客是在字符序列" :;" 之后放置代码行.如果您正在编写大部分单行脚本,或者可能是这样,可以sh为多行编写一行cmd,则以下可能没问题.不要忘记任何使用$?必须在你的下一个冒号之前,:因为:重置$?为0.

:; echo "Hi, I’m ${SHELL}."; exit $?
@ECHO OFF
ECHO I'm %COMSPEC%
Run Code Online (Sandbox Code Playgroud)

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

:; false; ret=$?
:; [ ${ret} = 0 ] || { echo "Program failed with code ${ret}." >&2; exit 1; }
:; exit
ECHO CMD code.
Run Code Online (Sandbox Code Playgroud)

跳过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%
Run Code Online (Sandbox Code Playgroud)

根据您的需要或编码风格,隔行扫描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%
Run Code Online (Sandbox Code Playgroud)

当然,通用评论可以使用字符序列: #:;#.该空间或分号是必需的,因为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%
Run Code Online (Sandbox Code Playgroud)

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

  • 请注意,这需要脚本具有`.cmd`扩展名,否则它将无法在Windows中运行.有没有解决方法? (4认同)

npo*_*aka 21

你可以试试这个:

#|| goto :batch_part
 echo $PATH
#exiting the bash part
exit
:batch_part
 echo %PATH%
Run Code Online (Sandbox Code Playgroud)

可能你需要使用/r/n新行而不是unix样式.如果我记得正确的话,unix新行不会被.bat脚本解释为新行.另一种方法是#.exe在路径中创建一个什么都不做的文件与我的回答类似: 是否可以在批处理文件中嵌入和执行VBScript而不使用临时文件?

编辑

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

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

echo $PATH
Run Code Online (Sandbox Code Playgroud)

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

  • 行尾的`/ r/n`会在bash脚本中引起很多麻烦,除非你用`#`(即`#/ r/n`)结束每一行 - 这样``\ r`将被处理作为评论的一部分而被忽略. (3认同)
  • 实际上,我发现`#2> NUL&ECHO欢迎使用%COMSPEC%.`设法隐藏`cmd`找不到```命令的错误.当您需要编写仅由`cmd`执行的独立行时(例如,在`Makefile`中),此技术非常有用. (2认同)

Bri*_*汤莱恩 11

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

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

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

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

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

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

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


A-D*_*ddy 10

前面的答案似乎涵盖了几乎所有选项,对我帮助很大。我在此处包含此答案只是为了演示我用于在同一文件中包含 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.
Run Code Online (Sandbox Code Playgroud)

概括

在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 部分删除。CR请务必为 Windows 部分保留回车 ( ) 字符。

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

  • 在Notepad++中只需选择*编辑&gt; EOL转换&gt; LF*,无需进行此类替换。无需每次都这样做,只需在“设置”&gt;“首选项”中将 LF 设置为默认值即可。否则[在Windows上有很多方法可以进行CRLF转换](/sf/ask/1425814701/) (3认同)

Vel*_*kan 8

您可以共享变量:

:;SET() { eval $1; }

SET var=value

:;echo $var
:;exit
ECHO %var%
Run Code Online (Sandbox Code Playgroud)


Tex*_*ler 7

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

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

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

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

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

# 2>nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #
Run Code Online (Sandbox Code Playgroud)

#行开头的字符使cmdbash行视为注释。

#行尾的字符用于bash注释掉该字符,正如 Brian Tompsett 在他的回答\r中指出的那样。如果没有这个,如果文件有行结尾,将抛出一个错误,这是.bash\r\ncmd

通过这样做# 2>nul,我们可以cmd忽略某些不存在的命令的错误#,同时仍然执行后面的命令。

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


用于echo忽略#上的字符cmd

我们可以使用echo它的输出重定向来在注释掉的区域插入cmd命令:bash

echo >/dev/null # >nul & echo Hello cmd! & rem ^
echo 'Hello bash!' #
Run Code Online (Sandbox Code Playgroud)

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


空的#.bat文件

echo >/dev/null # 1>nul 2> #.bat
# & echo Hello cmd! & del #.bat & rem ^
echo 'Hello bash!' #
Run Code Online (Sandbox Code Playgroud)

该行在on 时echo >/dev/null # 1>nul 2> #.bat创建一个空文件(或替换现有的,如果有),并且在 on 时不执行任何操作。#.batcmd#.batbash

即使.cmd​​#PATH

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

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


推荐:使用此处文档忽略cmd命令bash

:; echo 'Hello bash!';<<:
echo Hello cmd! & ^
:
Run Code Online (Sandbox Code Playgroud)

通过将^字符放在行尾,cmd我们可以转义换行符,并且通过用作:此处文档分隔符,分隔符行内容将不会对cmd. 这样,cmd只会在该:行结束后执行该行,与 具有相同的行为bash

如果你想在两个平台上都有多行并且只在块的末尾执行它们,你可以这样做:

:;( #
  :;  echo 'Hello'  #
  :;  echo 'bash!'  #
:; );<<'here-document delimiter'
(
      echo Hello
      echo cmd!
) & rem ^
here-document delimiter
Run Code Online (Sandbox Code Playgroud)

只要没有cmd精确的线路here-document delimiter,这个解决方案就应该有效。您可以更改here-document delimiter为任何其他文本。


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

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


小智 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
Run Code Online (Sandbox Code Playgroud)


Lol*_*ens 5

我使用这种技术来创建可运行的 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
}
Run Code Online (Sandbox Code Playgroud)

如上所述设置行结尾非常重要,因为它们可能会在不同平台上引起问题。此外,如果跳转标签周围缺少正确的行结尾,则在某些情况下 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
Run Code Online (Sandbox Code Playgroud)
  • 第一行在 cmd 中回显,并且在 sh 上不打印任何内容。这是因为@in sh 抛出一个错误,该错误通过管道传输到/dev/null注释开始之后。在 cmd 上,管道 to/dev/null失败,因为该文件在 Windows 上无法识别,但由于 Windows 没有检测#为注释,因此错误被通过管道传输到nul. 然后它会关闭回声。因为整行前面有一个,所以@它不会在 cmd 上打印。
  • 第二个定义了::,它在 cmd 中启动注释,在 sh 中进行 noop 。这样做的好处是::不会重置$?0. 它使用“:;是一个标签”技巧。
  • 现在我可以在 sh 命令前面加上它们::,它们在 cmd 中会被忽略
  • :: exitsh脚本结束后我可以编写cmd命令
  • 在 cmd 中只有第一行 (shebang) 有问题,因为它会打印command not found. 你必须自己决定是否需要它。