Один сценарий для запуска в пакетном режиме 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++ и сделал следующее:
Включите опцию просмотра символов конца строки (
View
>Show Symbol
>Show End of Line
). Возврат каретки будет отображаться какCR
персонажи.Найти и заменить (
Search
>Replace...
) и проверьтеExtended (\n, \r, \t, \0, \x...)
вариант.Тип
\r
вFind what :
поле и вычеркнутьReplace with :
поле, так что в нем ничего нет.Начиная с верхней части файла, нажмите
Replace
кнопка пока все каретки возвратятся (CR
) символы были удалены из верхней части Linux. Не забудьте оставить возврат каретки (CR
) символы для части Windows.
В результате каждая команда Linux заканчивается только переводом строки (LF
) и каждая команда Windows заканчивается переводом каретки и переводом строки (CR
LF
).
Следующее работает для меня без каких-либо ошибок или сообщений об ошибках в 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.