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


96

Чи можна написати один файл сценарію, який виконується як у Windows (розглядається як .bat), так і в Linux (через Bash)?

Я знаю основний синтаксис обох, але не зрозумів. Можливо, це може використати якийсь незрозумілий синтаксис Баша або якийсь збій пакетного процесора Windows.

Команда для виконання може бути лише одним рядком для виконання іншого сценарію.

Мотивація полягає в тому, щоб мати лише одну команду завантаження програми як для Windows, так і для Linux.

Оновлення: Потреба в "рідному" сценарії оболонки полягає в тому, що йому потрібно вибрати правильну версію інтерпретатора, відповідати певним відомим змінним середовища тощо. Встановлення додаткових середовищ, таких як CygWin, не є кращим - я хотів би зберегти концепцію " завантажити та запустити ".

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


9
Подивіться на Perl або Python. Це мови сценаріїв, доступні на обох платформах.
a_horse_with_no_name

groovy, python, ruby ​​хтось? stackoverflow.com/questions/257730 / ...
Kalpesh Soni

Groovy було б чудово мати у всіх системах за замовчуванням, я б сприйняв це як мову сценаріїв оболонки. Може, колись :)
Ондра Жижка,

1
Дивіться також: github.com/BYVoid/Batsh
Дейв Джарвіс

2
Вагомим випадком використання цього є підручники - щоб мати можливість просто сказати людям "запустити цей маленький сценарій", не маючи пояснення, що "якщо ви працюєте в Windows, використовуйте цей маленький скрипт, але якщо ви працюєте на Linux або Mac, спробуйте це натомість, який я насправді не тестував, оскільки працюю в Windows ". На жаль, Windows не має базових команд, подібних до unix, таких як cpвбудовані, або навпаки, тому написання двох окремих сценаріїв може бути педагогічно кращим, ніж передові методи, показані тут.
Qwertie

Відповіді:


92

Що я зробив, так це використовую синтаксис мітки cmd як маркер коментаря . Символ мітки, двокрапка ( :), еквівалентний trueбільшості оболонок POSIXish. Якщо ви відразу ж слідуєте за символом мітки іншим символом, який не можна використовувати в a 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інтерпретує його. У цьому випадку ми переконуємось, що роздільник нашого гередока вказується в цитатах (щоб уникнути 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код можуть мати сенс , а можуть і не мати сенсу. Використання гередоків - один із методів виконання такого переплетення. Однак це можна продовжити за допомогою 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

1
Якщо ви пишете пакетні файли, я б рекомендував просто назвати їх .cmdі позначити як виконувані. Таким чином, виконання сценарію із оболонки за замовчуванням у Windows або unix буде працювати (перевіряється лише за допомогою bash). Оскільки я не можу придумати спосіб включити шебанг, не заставляючи cmd голосно скаржитися, ви завжди повинні викликати сценарій через оболонку. Тобто, не забудьте скористатися execlp()або execvp()(я думаю, system()що це "правильний" спосіб для вас), а неexecl() або execv()(ці останні функції працюватимуть лише на сценаріях із shebangs, оскільки вони переходять безпосередньо до ОС).
binki

Я пропустив обговорення розширень файлів, тому що писав свій портативний код у розкладках (наприклад, <Exec/>MSBuild Task ), а не як незалежні пакетні файли. Можливо, якщо у мене буде трохи часу в майбутньому, я
оновлю

23

Ви можете спробувати це:

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

Ймовірно, вам доведеться використовувати /r/nяк новий рядок замість стилю unix. Якщо я пам'ятаю, виправлення, новий рядок unix не інтерпретується як новий рядок .batскриптами. Інший спосіб - створити #.exeфайл у шляху, який нічого не робить у подібно до моєї відповіді тут: Чи можна вбудовувати та виконувати VBScript у пакетному файлі, не використовуючи тимчасовий файл?

EDIT

Відповідь Бінкі майже ідеальна, але все ще може бути вдосконалена:

:<<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призведе до їх розриву.

Таким чином, той, хто використовує сценарій, і в будь-якому середовищі, він буде працювати.

Я використовую цей метод разом із створенням портативних Make-файлів!


6

Наступне працює для мене без помилок та повідомлень про помилки в Bash 4 та Windows 10, на відміну від відповідей вище. Я називаю файл "whatever.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

3

Ви можете ділитися змінними:

:;SET() { eval $1; }

SET var=value

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

2

Існує кілька способів виконання різних команд на одному сценарії bashта cmdз ним.

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

Що стосується змушення bashігнорувати cmdрядки, то існує кілька способів. Я перелічив кілька способів зробити це, не порушуючи cmdкоманд:

Неіснуюча #команда (не рекомендується)

Якщо під час запуску сценарію немає #доступної команди cmd, ми можемо зробити це:

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

#Символ на початку cmdлінії становить bashрозглядати цей рядок як коментар.

#Символ в кінці bashрядка використовується закомментировать \rхарактер, так як Брайан Tompsett зазначив в своїй відповіді . Без цього 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.


Краще пізно, ніж ніколи ... це допомогло мені більше, ніж інші відповіді. Дякую!!
A-Diddy,

2

Попередні відповіді охоплюють майже всі варіанти і мені дуже допомогли. Я включаю цю відповідь тут лише для демонстрації механізму, який я використовував для включення як сценарію Bash, так і сценарію CMD Windows у той самий файл.

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символи return ( ) каретки не будуть видалені з верхньої частини Linux. Не забудьте залишити CRсимволи return ( ) для каретки для частини Windows.

Результатом має стати те, що кожна команда Linux закінчується лише подачею рядка ( LF), а кожна команда Windows закінчується поверненням каретки та подачею рядків ( CR LF).


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 не вдається, оскільки файл не розпізнається у вікнах, але оскільки Windows не виявляє #як коментар, помилка передається nul. Потім він відлунює. Оскільки перед цілим рядком стоїть @символ, який не отримує друк на cmd.
  • Другий визначає ::, що починає коментар у cmd, з нупом у sh. Це має перевагу, ::яка не скидається$? в 0. Він використовує :;трюк " це мітка".
  • Тепер я можу додавати команди sh до, ::і вони ігноруються в cmd
  • На :: exit скрипті sh закінчується, і я можу писати команди cmd
  • Лише перший рядок (shebang) є проблематичним в cmd, оскільки він буде надрукований command not found. Ви повинні вирішити самі, потрібно це вам чи ні.

0

Мені це знадобилося для деяких моїх сценаріїв встановлення пакунків Python. Більшість речей між файлом sh та bat однакові, але деякі речі, такі як обробка помилок, відрізняються. Один із способів зробити це наступним:

common.inc
----------
common statement1
common statement2

Потім ви викликаєте це з bash script:

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 і запустити їх, незважаючи на тип os. Або ви можете просто створити сценарій обгортки Ant, який буде аналізувати тип ОС та запускати відповідний скрипт bat або bash.


5
Це здається грубим зловживанням цими інструментами. Якщо ви хочете уникнути власне запитань (запуск в рідній оболонці), то вам слід запропонувати універсальну мову сценаріїв, наприклад python, а не інструмент побудови, який, можливо, може бути зловживаний як заміна належної мови сценаріїв.
ArtOfWarfare
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.