【问题标题】:Single script to run in both Windows batch and Linux Bash?在 Windows 批处理和 Linux Bash 中运行的单个脚本?
【发布时间】:2013-07-04 20:15:17
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 查看 Perl 或 Python。这些是两个平台上都可用的脚本语言。
  • groovy、python、ruby 有人吗? stackoverflow.com/questions/257730/…
  • Groovy 默认在所有系统上都很棒,我会把它当作一个 shell 脚本语言。也许有一天:)
  • 教程是一个引人注目的用例——能够告诉人们“运行这个小脚本”而不必解释“如果你在 Windows 上,使用这个小脚本,但是如果你在 Linux 或 Mac 上,试试这个,因为我在 Windows 上,所以我没有实际测试过。”遗憾的是,Windows 没有内置类似 unix 的基本命令,例如 cp,反之亦然,因此编写两个单独的脚本在教学上可能比此处显示的高级技术更好。

标签: bash batch-file


【解决方案1】:

编辑

binki's answer 几乎完美,但仍有待改进:

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

echo $PATH

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


你可以试试这个:

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

您可能需要使用 /r/n 作为新行而不是 unix 样式。如果我没记错的话,.bat 脚本不会将 unix 新行解释为新行。另一种方法是创建一个#.exe 路径中的文件与我的回答类似:Is it possible to embed and execute VBScript within a batch file without using a temporary file?

【讨论】:

  • 行尾的 /r/n 会在 bash 脚本中引起很多麻烦,除非您以# 结束每一行(即#/r/n) - 这样\r 将被处理作为评论的一部分并被忽略。
  • 其实我发现# 2&gt;NUL &amp; ECHO Welcome to %COMSPEC%. 设法隐藏了# 命令没有被cmd 找到的错误。当您需要编写仅由cmd 执行的独立行时(例如,在Makefile 中),此技术很有用。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      我的一些 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
      

      【讨论】:

        【解决方案5】:

        之前的答案似乎涵盖了几乎所有选项,并且对我帮助很大。我在这里包含这个答案只是为了演示我用来在同一个文件中包含 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 &gt;/dev/null # &gt;nul &amp; GOTO WINDOWS &amp; 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. Find what : 字段中输入 \r 并将 Replace with : 字段清空,因此其中没有任何内容。

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

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

        【讨论】:

        【解决方案6】:

        您可以共享变量:

        :;SET() { eval $1; }
        
        SET var=value
        
        :;echo $var
        :;exit
        ECHO %var%
        

        【讨论】:

          【解决方案7】:

          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&gt;nul,我们欺骗cmd 忽略某些不存在的# 命令的错误,同时仍执行后面的命令。

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


          使用echo 忽略cmd 上的# 字符

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

          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 &gt;/dev/null # 1&gt;nul 2&gt; #.bat 行在cmd 上创建一个空的#.bat 文件(或替换现有的#.bat,如果有的话),而在bash 上不执行任何操作。

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

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

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


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

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

          通过将^ 字符放在cmd 行的末尾,我们将转义换行符,并且通过使用: 作为此处文档的分隔符,分隔符行内容对@ 没有影响987654383@。这样,cmd 只会在: 行结束后执行其行,与bash 具有相同的行为。

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

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

          只要不存在与 here-document delimiter 完全相同的 cmd 行,此解决方案就可以工作。您可以将here-document delimiter 更改为任何其他文本。


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

          这些解决方案必须以\r\n 作为换行符保存到文件中,否则它们将无法在cmd 上运行。

          【讨论】:

          • 迟到总比没有好...这比其他答案对我的帮助更大。谢谢!!
          【解决方案8】:

          与上面的答案不同,以下内容适用于我,而 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
          

          【讨论】:

            【解决方案9】:

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

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

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

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

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

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

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

            【讨论】:

              【解决方案10】:

              我所做的是use cmd’s label syntax as comment marker。标签字符,冒号 (:),在大多数 POSIXish shell 中等同于 true。如果您在标签字符后面紧跟另一个不能在GOTO 中使用的字符,那么评论您的cmd 脚本不会影响您的cmd 代码。

              hack 是在字符序列“:;”之后放置代码行。如果您主要编写单行脚本,或者在可能的情况下,可以为多行 cmd 编写一行 sh,则以下可能没问题。不要忘记$? 的任何使用都必须在下一个冒号: 之前,因为: 会将$? 重置为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%
              

              Universal cmets 当然可以用字符序列: #:;# 来完成。空格或分号是必需的,因为sh 认为如果# 不是标识符的第一个字符,则它是命令名称的一部分。例如,在使用 GOTO 方法拆分代码之前,您可能希望在文件的第一行中编写通用 cmets。然后你可以告诉你的读者你的脚本为什么写得这么奇怪:

              : # 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.)。

              【讨论】:

              • 请注意,这需要脚本具有.cmd 扩展名,否则它将无法在 Windows 中运行。有什么解决办法吗?
              • 如果您正在编写批处理文件,我建议您将它们命名为 .cmd 并将它们标记为可执行文件。这样,在 Windows 或 unix 上从默认 shell 执行脚本都可以工作(仅使用 bash 测试)。因为我想不出一种在不让 cmd 大声抱怨的情况下包含 shebang 的方法,所以您必须始终通过 shell 调用脚本。即,一定要使用execlp()execvp()(我认为system() 这样做对你来说是“正确”的方式)而不是execl()execv()(后面的这些功能只适用于带有shebangs 的脚本,因为他们更直接地进入操作系统)。
              • 我省略了关于文件扩展名的讨论,因为我是在 shellouts(例如,&lt;Exec/&gt; MSBuild Task)中编写我的可移植代码,而不是作为独立的批处理文件。也许如果将来有时间,我会用这些 cmets 中的信息更新答案……
              【解决方案11】:

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

              【讨论】:

              • 这似乎是对这些工具的严重滥用。如果您想避免实际问题(在本机 shell 中运行),那么您应该建议一种通用脚本语言,例如 python,而不是一种恰好能够被滥用作为适当脚本语言的替代品的构建工具。跨度>
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2013-07-30
              • 1970-01-01
              • 2011-06-15
              • 2021-10-27
              • 1970-01-01
              • 2013-01-05
              • 1970-01-01
              相关资源
              最近更新 更多