Одиночный script для запуска как в ОС Windows, так и в Linux Bash?

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

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

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

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

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

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

Ответ 1

Что я сделал, это использовать синтаксис меток cmds в качестве маркера комментариев. Символ метки, двоеточие (:), эквивалентен true в большинстве оболочек POSIXish. Если вы сразу же следуете символу метки другим символом, который не может использоваться в GOTO, комментирование вашего cmd script не должно влиять на ваш код 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 интерпретирует его. В этом случае мы убеждаемся, что наш разделитель heredocs оба указан (чтобы 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 для разделения кода. Затем вы можете сообщить своему читателю, почему ваш script написан так странно:

: # 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 и cmd -совместимых скриптов без серьезных побочных эффектов, насколько я знаю (и без вывода cmd output '#' is not recognized as an internal or external command, operable program or batch file.).

Ответ 2

вы можете попробовать следующее:

#|| goto :batch_part
 some unix commands
#exiting the bash part
exit
:batch_part
 some windows commands

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

Ниже для меня работает без ошибок или сообщений об ошибках с Bash 4 и Windows 10, в отличие от приведенных выше ответов. Я называю файл "whatever.cmd", do 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

Ответ 5

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

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

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

Необязательная команда # (не рекомендуется)

Если в cmd нет команды #, когда выполняется script, мы можем сделать это:

# 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.

Ответ 6

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

:;SET() { eval $1; }

SET var=value

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

Ответ 7

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