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

96

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

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

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

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

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

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

Ондра Жижка
источник
9
Загляните в Perl или Python. Это языки сценариев, доступные на обеих платформах.
a_horse_with_no_name 07
заводной, питон, рубиновый? stackoverflow.com/questions/257730/…
Калпеш Сони
Groovy было бы здорово иметь по умолчанию во всех системах, я бы воспринял его как язык сценариев оболочки. Может быть, когда-нибудь :)
Ондра Жижка
1
См. Также: github.com/BYVoid/Batsh
Дэйв Джарвис,
2
Убедительным вариантом использования этого являются учебные пособия - чтобы иметь возможность просто сказать людям «запустить этот небольшой скрипт», не объясняя, что «если вы работаете в Windows, используйте этот небольшой скрипт, но если вы работаете в Linux или Mac, попробуйте это». вместо этого, что я на самом деле не тестировал, потому что я использую Windows ". К сожалению, в Windows нет базовых unix-подобных команд, таких как cpвстроенные или наоборот, поэтому написание двух отдельных сценариев может быть педагогически лучше, чем продвинутые методы, показанные здесь.
Qwertie 01

Ответы:

92

Что я сделал, так это использовал синтаксис метки cmd в качестве маркера комментария . Символ метки, двоеточие ( :), эквивалентен trueбольшинству POSIX-оболочек. Если вы сразу же следуете за символом метки другим символом, который нельзя использовать в 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 /programming/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.).

бинки
источник
5
Обратите внимание, что для этого у сценария должно быть .cmdрасширение, иначе он не будет работать в Windows. Есть ли обходной путь?
jviotti 04
1
Если вы пишете командные файлы, я бы рекомендовал просто назвать их .cmdи пометить как исполняемые. Таким образом, выполнение сценария из оболочки по умолчанию в Windows или unix будет работать (проверено только с помощью bash). Поскольку я не могу придумать способ включить shebang, не заставляя cmd громко жаловаться, вы всегда должны вызывать сценарий через оболочку. Т.е. обязательно используйте execlp()или execvp()(я думаю system(), это «правильный» для вас способ), а не execl()или execv()(эти последние функции будут работать только в сценариях с shebang, потому что они идут напрямую в ОС).
binki
Я пропустил обсуждение расширений файлов, потому что я писал свой переносимый код в оболочках (например, <Exec/>MSBuild Task ), а не в виде независимых пакетных файлов. Может быть, если у меня будет время в будущем, я дополню ответ информацией из этих комментариев…
binki
23

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

#|| 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 по-прежнему будет производить избыточный вывод ...

нпочмака
источник
3
/r/nв конце строк вызовет много головной боли в сценарии bash, если вы не заканчиваете каждую строку знаком #(т.е. #/r/n) - в этом случае \rони будут рассматриваться как часть комментария и игнорироваться.
Гордон Дэвиссон
2
На самом деле, мне # 2>NUL & ECHO Welcome to %COMSPEC%.удается скрыть ошибку о том, что #команда не найдена cmd. Этот метод полезен, когда вам нужно написать отдельную строку, которая будет выполняться только cmd(например, в a Makefile).
binki
11

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

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

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

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

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

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

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

Брайан Томпсетт - 莱恩
источник
6

Следующее работает для меня без каких-либо ошибок или сообщений об ошибках с Bash 4 и Windows 10, в отличие от ответов выше. Я называю файл «something.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
Ремик Зиемлински
источник
3

Есть несколько способов выполнения разных команд в одном 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.

Tex Killer
источник
Лучше поздно, чем никогда ... это помогло мне больше, чем другие ответы. Спасибо!!
A-Diddy
3

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

:;SET() { eval $1; }

SET var=value

:;echo $var
:;exit
ECHO %var%
Велкан
источник
2

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

A-Diddy
источник
1

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

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

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

монитор
источник
5
Это похоже на грубое злоупотребление этими инструментами. Если вы хотите избежать фактического вопроса (работающего в собственной оболочке), вам следует предложить универсальный язык сценариев, такой как python, а не инструмент сборки, который может быть неправильно использован в качестве замены правильного языка сценариев.
ArtOfWarfare