Один сценарий для запуска в пакетном режиме Windows и Linux Bash?

Можно ли написать один файл сценария, который выполняется как в Windows (рассматривается как.bat), так и в Linux (через Bash)?

Я знаю основной синтаксис обоих, но не понял. Возможно, он может использовать какой-то неясный синтаксис Bash или какой-то сбой пакетного процессора Windows.

Команда для выполнения может быть просто одной строкой для выполнения другого сценария.

Мотивация состоит в том, чтобы иметь только одну команду загрузки приложения для Windows и Linux.

Обновление: потребность в "родном" сценарии оболочки системы заключается в том, что ему нужно выбрать правильную версию интерпретатора, соответствовать определенным общеизвестным переменным среды и т. Д. Установка дополнительных сред, таких как CygWin, не является предпочтительной - я хотел бы сохранить концепцию " скачать и запустить ".

Единственный другой язык для Windows - это Windows Scripting Host - WSH, который по умолчанию установлен с 98 года.

12 ответов

Решение

Я использовал синтаксис метки cmd в качестве маркера комментария. Метка символа, двоеточие (:), эквивалентно true в большинстве POSIXish снарядов. Если вы сразу следуете за меткой за другим символом, который нельзя использовать в 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 так, чтобы sh относится к cmd код как неиспользуемая строка и 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%

В зависимости от ваших потребностей или стиля кодирования, переплетение cmd а также sh код может иметь или не иметь смысла. Использование 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://stackru.com/questions/17510688
: # for details.
:; echo "This is ${SHELL}"; exit
@ECHO OFF
ECHO This is %COMSPEC%

Таким образом, некоторые идеи и способы достижения sh а также cmd-совместимые скрипты без серьезных побочных эффектов, насколько я знаю (и без cmd выход '#' is not recognized as an internal or external command, operable program or batch file.).

Вы можете попробовать это:

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

Вероятно, вам нужно будет использовать /r/n как новая строка вместо стиля Unix. Если я правильно помню, новая строка Unix не интерпретируется как новая строка .bat сценарии. Другой способ заключается в создании #.exe файл в пути, который ничего не делает так же, как мой ответ здесь: возможно ли встроить и выполнить VBScript в пакетном файле без использования временного файла?

РЕДАКТИРОВАТЬ

Ответ Бинки почти идеален, но все еще может быть улучшен:

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

echo $PATH

Он снова использует : хитрость и многострочный комментарий. Такие файлы, как cmd.exe (по крайней мере, для windows10), работают без проблем с EOL в стиле Unix, поэтому убедитесь, что ваш скрипт конвертирован в формат linux. (один и тот же подход использовался ранее здесь и здесь) . Хотя использование shebang все равно даст избыточный вывод...

Я хотел прокомментировать, но могу только добавить ответ на данный момент.

Приведенные техники превосходны, и я их тоже использую.

Трудно сохранить файл, в котором есть два вида разрывов строк, /n для части bash и /r/n для части окон. Большинство редакторов пытаются применить общую схему разрыва строки, угадывая, какой тип файла вы редактируете. Кроме того, большинство методов передачи файла через Интернет (особенно в виде текстового файла или файла сценария) приводят к отмыванию разрывов строк, так что вы можете начать с одного вида разрыва строки и в конечном итоге с другим. Если вы сделали предположения о переносе строк, а затем передали свой сценарий кому-то другому, он мог бы обнаружить, что он не работает для них.

Другая проблема - это монтируемые в сети файловые системы (или компакт-диски), которые совместно используются различными типами систем (особенно там, где вы не можете управлять программным обеспечением, доступным для пользователя).

Поэтому следует использовать разрыв строки DOS /r/n а также защитить скрипт bash от DOS /r оставив комментарий в конце каждой строки (#). Вы также не можете использовать продолжения строки в bash, потому что /r заставит их сломаться.

Таким образом, кто бы ни использовал скрипт и в любой среде, он будет работать.

Я использую этот метод в сочетании с созданием портативных Makefile!

Предыдущие ответы, кажется, охватывают почти все варианты и мне очень помогли. Я включил этот ответ здесь, просто чтобы продемонстрировать механизм, который я использовал для включения сценария 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.

Резюме

В линуксе

Первая строка (echo >/dev/null # >nul & GOTO WINDOWS & rem ^) будет игнорироваться, и скрипт будет проходить через каждую строку, следующую сразу за ней, пока exit 0 команда выполнена. однажды exit 0 Достигнута, выполнение сценария завершится, игнорируя команды Windows под ним.

В винде

Первая строка выполнит GOTO WINDOWS команда, пропуская команды Linux сразу после нее и продолжая выполнение на :WINDOWS линия.

Удаление возврата каретки в Windows

Поскольку я редактировал этот файл в Windows, мне приходилось систематически удалять возврат каретки (\r) из команд Linux, иначе я получил ненормальные результаты при запуске части 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 заканчивается переводом каретки и переводом строки (CRLF).

Следующее работает для меня без каких-либо ошибок или сообщений об ошибках в Bash 4 и Windows 10, в отличие от ответов выше. Я называю файл "what.cmd", делаю chmod +x сделать его исполняемым в Linux и сделать так, чтобы он имел окончание строки Unix (dos2unix) держать башку в покое.

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

Вы можете поделиться переменными:

:;SET() { eval $1; }

SET var=value

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

Есть несколько способов выполнения различных команд на bash а также cmd с тем же сценарием.

cmd будет игнорировать строки, начинающиеся с :;, как упоминалось в других ответах. Он также будет игнорировать следующую строку, если текущая строка заканчивается командой rem ^ как ^ символ будет избегать разрыва строки, а следующая строка будет рассматриваться как комментарий rem,

Что касается изготовления bash игнорировать cmd линии, есть несколько способов. Я перечислил несколько способов сделать это, не нарушая cmd команды:

Несуществующий # команда (не рекомендуется)

Если нет # команда доступна на cmd когда скрипт запущен, мы можем сделать это:

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

# персонаж в начале cmd линия делает bash рассматривать эту строку как комментарий.

# персонаж в конце 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 строка создает пустую #.bat файл в то время как на cmd (или заменяет существующий #.bat, если есть), и ничего не делает, пока на bash,

Этот файл будет использоваться cmd строка (и), которая следует, даже если есть некоторые другие # команда на PATH,

del #.bat команда на cmd специфический код удаляет файл, который был создан. Вы должны сделать это только в последний раз cmd линия.

Не используйте это решение, если #.bat файл может находиться в вашем текущем рабочем каталоге, так как этот файл будет удален.


Рекомендуется: используйте здесь-документ, чтобы игнорировать cmd команды на bash

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

Пока нет cmd точно here-document delimiter, это решение должно работать. Ты можешь измениться here-document delimiter на любой другой текст.


Во всех представленных решениях команды будут выполняться только после последней строки, что делает их поведение согласованным, если они выполняют одно и то же на обеих платформах.

Эти решения должны быть сохранены в файлы с \r\n как разрывы строк, иначе они не будут работать на cmd ,

Я использую эту технику для создания исполняемых файлов 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, Тогда это делает эхо. Потому что всей строке предшествует @ это не получает printet на cmd.
  • Второй определяет ::, который начинает комментарий в cmd, чтобы noop в sh. Это имеет то преимущество, что :: не сбрасывается $? в 0, Он использует ":; это ярлык "трюк.
  • Теперь я могу добавить команды sh :: и они игнорируются в cmd
  • На :: exit скрипт sh заканчивается, и я могу писать команды cmd
  • Только первая строка (shebang) проблематична в cmd, так как она напечатает command not found, Вы сами должны решить, нужно вам это или нет.

Мне это нужно для некоторых сценариев установки пакетов 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

Ответ Бинки был отличным, но не нашел решения проблемы вывода ошибки shebang в Windows.

      C:\>#!/bin/bash
'#!' is not recognized as an internal or external command,
operable program or batch file.

Я обнаружил, что вы можете заставить Windows перенаправлять вывод ошибки на ноль с помощью умного кодирования разрыва строки. Windows учитывает разрывы строк «/r/n» и «/n», но не «/r». Поэтому, если мы сделаем первый разрыв строки «/r», Windows будет рассматривать первые две строки как одну и перенаправит вывод ошибки на ноль.

      #!/bin/bash
# > nul 2>&1

Windows по-прежнему будет отображать этот код, но, по крайней мере, сохранит чистоту потока вывода ошибок. MacOS и Linux рассматривают эти строки как отдельные, поэтому шебанг распознается, а следующая строка игнорируется как комментарий. Я тестировал это на macOS 13.2.1, Windows 10 22H2 и Ubuntu 22.04.2 LTS.

Обратной стороной этого является то, что играть с кодировкой разрыва строки просто напрашивается проблема, по крайней мере, в Windows. Например, Блокнот создает новые разрывы строк на основе стиля первого разрыва строки. Но PowerShell ISE использует разрывы строк вокруг измененного текста, чтобы определить, какой стиль использовать. И даже если вы все сделаете правильно, некоторые скриптовые платформы будут перезаписывать разрывы строк в зависимости от целевой ОС.

Попробуйте мой проект BashWin на https://github.com/skanga/bashwin, который использует BusyBox для большинства команд Unix

Существуют независимые от платформы инструменты сборки, такие как Ant или Maven с синтаксисом xml (на основе Java). Таким образом, вы можете переписать все свои скрипты в Ant или Maven и запустить их, несмотря на тип os. Или вы можете просто создать скрипт-обертку Ant, который будет анализировать тип ОС и запускать соответствующий скрипт bat или bash.

Другие вопросы по тегам