web-dev-qa-db-ja.com

WindowsバッチとLinux Bashの両方で実行する単一のスクリプトですか?

Windows(.batとして処理)とLinux(Bash経由)の両方で実行される単一のスクリプトファイルを作成することはできますか?

両方の基本的な構文は知っていますが、わかりませんでした。 Bashの不明瞭な構文やWindowsバッチプロセッサの不具合を悪用する可能性があります。

実行するコマンドは、他のスクリプトを実行するための1行だけです。

動機は、WindowsとLinuxの両方で単一のアプリケーションブートコマンドを使用することです。

更新:システムの「ネイティブ」シェルスクリプトの必要性は、適切なインタープリターバージョンを選択し、特定の既知の環境変数などに準拠する必要があることです。CygWinなどの追加環境のインストールは好ましくありません-私はd「ダウンロードして実行」という概念を維持したい。

Windowsで考慮すべき他の言語は、Windows Scripting Host-WSHのみです。これは、98年以降デフォルトで事前設定されています。

85
Ondra Žižka

私がやったことは cmdのラベル構文をコメントマーカーとして使用 です。ラベル文字であるコロン(:)は、ほとんどのPOSIXishシェルのtrueと同等です。ラベル文字の直後にGOTOで使用できない別の文字が続く場合、cmdスクリプトにコメントを付けてもcmdコードに影響はありません。

ハックは、文字列「:;」の後にコードの行を置くことです。主に1行のスクリプトを記述している場合、または場合によっては、shの多くの行に対してcmdの1行を記述できる場合は、次のようにすればよいでしょう。 $?:を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が解釈するようにすることです。この場合、ヒアドックの区切り文字は必ず引用符で囲み(shで実行するときにshがその内容に対して何らかの解釈を行うのを防ぐため)、:で始まるようにして、cmd:で始まる他の行と同様にスキップするようにします。

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

ニーズまたはコーディングスタイルに応じて、cmdおよびshコードをインターレースすることは意味がある場合とない場合があります。ヒアドキュメントの使用は、このようなインターレースを実行する1つの方法です。ただし、これは 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%

したがって、私の知る限り、深刻な副作用なしに(およびsh出力'#' is not recognized as an internal or external command, operable program or batch file.なしで)cmdおよびcmd互換スクリプトを実現するためのいくつかのアイデアと方法。

76
binki

これを試すことができます:

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

おそらく、Unixスタイルの代わりに/r/nを新しい行として使用する必要があります。覚えている場合、Unixの新しい行は、.batスクリプトによって新しい行として解釈されません。ここに私の答えと同様の方法で何もしないパスの#.exeファイル: 一時ファイルを使用せずにバッチファイル内にVBScriptを埋め込み、実行することは可能ですか?

[〜#〜] edit [〜#〜]

binki's answer はほぼ完璧ですが、それでも改善できます:

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

echo $PATH

再度:トリックと複数行コメントを使用します。cmd.exe(少なくともwindows10では)のような外観は、unixスタイルのEOLでは問題なく動作するため、スクリプトをLinux形式に変換してください。 ( here および here の前に同じアプローチが使用されていました)。 Shebangを使用しても冗長な出力が生成されますが...

19
npocmaka

コメントしたかったのですが、現時点では回答を追加することしかできません。

与えられたテクニックは優れており、私もそれらを使用しています。

2種類の改行を含むファイルを保持することは困難です。bash部分の/nとwindows部分の/r/nです。ほとんどのエディターは、編集しているファイルの種類を推測することにより、一般的な改行スキームを実行しようとします。また、インターネット経由でファイルを転送するほとんどの方法(特にテキストファイルまたはスクリプトファイルとして)は改行を洗浄するため、ある種類の改行から始めて別の種類の改行で終わる可能性があります。改行について想定してから、スクリプトを使用するために他の誰かにスクリプトを渡した場合、彼らにとってはうまくいかないかもしれません。

もう1つの問題は、異なるシステムタイプ間で共有されるネットワークマウントファイルシステム(またはCD)です(特に、ユーザーが利用できるソフトウェアを制御できない場合)。

したがって、/r/nのDOS改行を使用し、各行の最後にコメントを入れることで/rからbashスクリプトを保護する必要があります(#)。また、/rにより改行されるため、bashで行継続を使用することもできません。

この方法で、スクリプトを使用する人は誰でも、どのような環境でも、スクリプトは機能します。

このメソッドは、ポータブルMakefileの作成と組み合わせて使用​​します!

上記の回答とは異なり、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
6

同じスクリプトでbashcmdで異なるコマンドを実行する方法はいくつかあります。

cmdは、他の回答で述べたように、:;で始まる行を無視します。また、rem ^文字は改行をエスケープし、次の行はremによってコメントとして扱われるため、現在の行がコマンド^で終わる場合、次の行も無視します。

bashcmd行を無視するようにするには、複数の方法があります。 cmdコマンドを壊さずにそれを行う方法をいくつか列挙しました。

存在しない#コマンド(非推奨)

スクリプトの実行時にcmdで使用可能な#コマンドがない場合、これを行うことができます。

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

cmd行の先頭にある#文字は、bashがその行をコメントとして扱うようにします。

ブライアントムプセットが his answer で指摘したように、bash行の最後にある#文字は、\r文字をコメント化するために使用されます。これがないと、ファイルに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行は、cmd上で空の#.batファイルを作成し(または、存在する場合は既存の#.batを置き換えます)、bash上では何もしません。

このファイルは、cmdに他の#コマンドがある場合でも、後続のPATH行で使用されます。

cmd固有のコードのdel #.batコマンドは、作成されたファイルを削除します。これは、最後のcmd行でのみ行う必要があります。

#.batファイルが現在の作業ディレクトリにある場合は、このソリューションを使用しないでください。そのファイルは消去されます。


推奨: here-document を使用してcmdbashコマンドを無視する

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

正確にhere-document delimiterを含むcmd行がない限り、このソリューションは機能します。 here-document delimiterを他のテキストに変更できます。


提示されたすべてのソリューションでは、コマンドは最後の行の後にのみ実行され、同じことを行うと動作が一貫します両方のプラットフォーム。

これらのソリューションは、\r\nを改行としてファイルに保存する必要があります。そうしないと、cmdで動作しません。

2
Tex Killer

変数を共有できます:

:;SET() { eval $1; }

SET var=value

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

前の回答はほとんどすべてのオプションを網羅しているようで、私を大いに助けてくれました。ここでは、同じファイルに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コマンドは復帰と改行(CRLF)で終わります。

1
A-Diddy

この手法を使用して、実行可能な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でprintetを取得しません。
  • 2番目は、cmdでコメントを開始する::をshでnoopに定義します。これには、::$?0にリセットしないという利点があります。 「:;はラベルです」トリックを使用します。
  • これで、shコマンドの前に::を追加でき、cmdでは無視されます
  • :: exitでshスクリプトが終了し、cmdコマンドを書くことができます
  • command not foundを出力するため、cmdでは最初の行(Shebang)のみが問題になります。必要かどうかは自分で決める必要があります。
0
LolHens