<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Блоги: заметки с тегом cmd</title>
<link>https://blogengine.me/blogs/tags/cmd/</link>
<description>Автоматически собираемая лента заметок, написанных в блогах на Эгее</description>
<author></author>
<language>ru</language>
<generator>Aegea 11.0 (v4079e)</generator>

<itunes:subtitle>Автоматически собираемая лента заметок, написанных в блогах на Эгее</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit>no</itunes:explicit>

<item>
<title>cmd.sh</title>
<guid isPermaLink="false">127159</guid>
<link>https://bolknote.ru/all/cmd-sh/</link>
<pubDate>Sat, 06 Apr 2024 00:26:05 +0500</pubDate>
<author>Евгений Степанищев</author>
<comments>https://bolknote.ru/all/cmd-sh/</comments>
<description>
&lt;p&gt;&lt;a href="https://bolknote.ru/"&gt;Евгений Степанищев&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;Я тут случайно в спор ввязался — можно ли переписать бат-файл так, чтобы он запускался и выводил примерно одинаковый результат на трёх основных операционных системах — Виндоузе, Линуксе и МакОСи.&lt;/p&gt;
&lt;p&gt;Речь шла о вполне определённом файле, там запрограммировано небольшое меню и в зависимости от выбранного пункта запускается та или иная, одинаковая для всех ОС, последовательность команд.&lt;/p&gt;
&lt;p&gt;Мой собеседник мне не верил, когда я утверждал, что это вполне возможно, и требовал доказательств.&lt;/p&gt;
&lt;p&gt;Что ж. Основная моя идея была в том, чтобы заменить команды батника на вызовы баш-функций, которые я спрячу в специальных конструкциях. В Виндоузе файл обработает обычный &lt;tt&gt;cmd.exe&lt;/tt&gt;, на остальных операционках — &lt;tt&gt;bash&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;То, что мне предстояло адаптировать начиналось вполне стандартно:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="php"&gt;@ECHO off
CHCP 1251
CLS&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Что тут происходит? Выключается вывод команд на экран (при адаптации это можно игнорировать), ставится однобайтовая кодировка &lt;i&gt;CP1251&lt;/i&gt; (это хорошо бы учесть) и очищается экран.&lt;/p&gt;
&lt;p&gt;Поскольку «баш» этих команд не знает, надо определить три функции, спрятав их от &lt;tt&gt;cmd.exe&lt;/tt&gt;. Сделать это несложно. Команду «двоеточие» &lt;tt&gt;cmd.exe&lt;/tt&gt; трактует как начало метки и на дальнейшее не реагирует, а «баш» считает её пустой командой, которую можно точкой с запятой отделить от строки, которую мы хотим спрятать:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;:; @ECHO() { :;}; CLS() { clear; }; CHCP() { ENC=&amp;quot;$1&amp;quot;; }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Следующее, чему надо научиться — выводить текст в указанной в батнике кодировке. Это просто:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;:; ECHO.() { echo $@ | iconv -f &amp;quot;CP$ENC&amp;quot;; }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если теперь сделать файл запускаемым и добавить впереди &lt;tt&gt;#!/bin/bash&lt;/tt&gt; (и пренебречь тем, что &lt;tt&gt;cmd.exe&lt;/tt&gt; успевает ругнуться на эту строку до очистки экрана), у нас получится следующий файл, работающий во всех трёх операционках (кодировка должна быть &lt;i&gt;Windows-1251&lt;/i&gt;):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;#!/bin/bash
:; @ECHO() { :;}; CLS() { clear; }; CHCP() { ENC=&amp;quot;$1&amp;quot;; }
@ECHO off
CHCP 1251
CLS
:; ECHO.() { echo $@ | iconv -f &amp;quot;CP$ENC&amp;quot;; }
ECHO. Всем привет&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;А вот дальше сложнее. Само меню организовано в оригинале так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="php"&gt;SET /p opt=Введите цифру:

IF %opt%==1 GOTO dns_auto
IF %opt%==2 GOTO dnschange
IF %opt%==3 GOTO exit&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В «баше» &lt;tt&gt;goto&lt;/tt&gt; (переход к метке) отсутствует и это проблема. Аналогов тоже нет. Единственное, что тут можно сделать — использовать вызов функций, но синтаксис в «бате» и «баше» сильно различается.&lt;/p&gt;
&lt;p&gt;Как быть? Во-первых, сэмулировать функцией команду &lt;tt&gt;SET&lt;/tt&gt;, во-вторвых, упростить это место, чтобы меньше было писать кода на «баше»:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;:; SET() { shift; ECHO. -n &amp;quot;${@#*=}&amp;quot;; read -n1 &amp;quot;${1%%=*}&amp;quot;; echo; }
:; CALL() { eval &amp;quot;$(sed &amp;#039;s/%\(.*\)%/$\1/g&amp;#039; &amp;lt;&amp;lt;&amp;lt; &amp;quot;$1&amp;quot;)&amp;quot; 2&amp;gt;&amp;amp;-; }

SET /p opt=Введите цифру:
CALL :menu_%opt%&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Первая функция парсит команду &lt;tt&gt;SET&lt;/tt&gt; выводит строку после равно, ждёт ввода и записывает значение в имя переменной, указанной слева от равно.&lt;/p&gt;
&lt;p&gt;Вторая — заменяет &lt;tt&gt;CALL&lt;/tt&gt;. В ней &lt;tt&gt;%variable%&lt;/tt&gt; заменяется на &lt;tt&gt;$variable&lt;/tt&gt;, выполняется подстановка переменной и получившееся имя выполняется как команда или функция «баша». Конструкция &lt;tt&gt;2&amp;gt;&amp;amp;-&lt;/tt&gt; нужна, чтобы избежать вывода ошибки в ситуации, если пользователь введёт что-нибудь не то.&lt;/p&gt;
&lt;p&gt;Теперь надо как-то научиться определять функции так, чтобы их нормально «видел» и &lt;tt&gt;cmd.exe&lt;/tt&gt;, и «баш». В батнике функции — просто любое место программы, начинающееся с метки и заканчивающееся вызовом &lt;tt&gt;GOTO :EOF&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="php"&gt;:function_1
ECHO это якобы функция
GOTO :EOF&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В «баше» то же самое могло бы выглядеть, например, так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;function_1() {
    echo это функция
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Как это объединить? Я придумал следующий подход:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;:; GOTO() { :; };

GOTO ;#start
:function_1 (){
ECHO. это [якобы] функция
GOTO :EOF
:; }

:;#start&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В первой строке определяется пустая функция для «баша», потому что эта часть синтаксиса бат-файла нам не нужна.&lt;/p&gt;
&lt;p&gt;Ниже идёт строка &lt;tt&gt;GOTO ;#start&lt;/tt&gt;. С точки зрения «баша» тут две конструкции — &lt;tt&gt;GOTO&lt;/tt&gt;, которая вызывает определённую выше пустую функцию и &lt;tt&gt;#start&lt;/tt&gt; — строка комментария, так как в«баше» с «решётки» начинаются комментарии.&lt;/p&gt;
&lt;p&gt;В «батнике» же эта же строка будет означать переход к метке с именем &lt;tt&gt;;#start&lt;/tt&gt;. Этот переход нужен нам, чтобы «обогнуть» строки, которые определены ниже, иначе &lt;tt&gt;cmd.exe&lt;/tt&gt; начнёт сразу их выполнять, а нам этого не нужно. Их нельзя «спрятать» ниже основной программы, так как в «баше» функции должны определены раньше их вызова.&lt;/p&gt;
&lt;p&gt;Что происходит дальше? С точки зрения &lt;tt&gt;cmd.exe&lt;/tt&gt; ниже расположена метка &lt;tt&gt;:function_1&lt;/tt&gt; (как показывают мои эксперименты, часть после пробела просто отбрасывается), потом тело функции, команда её завершения &lt;tt&gt;GOTO :EOF&lt;/tt&gt; и метка с именем &lt;tt&gt;:;}&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;С точки зрения «баша» там определяется функция с именем &lt;tt&gt;:function_1&lt;/tt&gt;, ниже идут вызовы уже определённых мною функций &lt;tt&gt;ECHO.&lt;/tt&gt; и &lt;tt&gt;GOTO&lt;/tt&gt;, а ещё ниже — уже знакомая нам пустая команда &lt;tt&gt;:&lt;/tt&gt; и фигурная скобка, завершающая тело функции.&lt;/p&gt;
&lt;p&gt;Соединяем всё вместе и получается следующая программа:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;#!/bin/bash
:; @ECHO() { :;}; CLS() { clear; }; CHCP() { ENC=&amp;quot;$1&amp;quot;; }
@ECHO off
CHCP 1251
CLS

:; GOTO() { :; };
:; ECHO.() { echo $@ | iconv -f &amp;quot;CP$ENC&amp;quot;; }; PAUSE() { read; }; 
:; SET() { shift; ECHO. -n &amp;quot;${@#*=}&amp;quot;; read &amp;quot;${1%%=*}&amp;quot;; }
:; CALL() { eval &amp;quot;$(sed &amp;#039;s/%\(.*\)%/$\1/g&amp;#039; &amp;lt;&amp;lt;&amp;lt; &amp;quot;$1&amp;quot;)&amp;quot; 2&amp;gt;&amp;amp;-; }

GOTO ;#start

:menu_1 (){
ECHO. Пункт первый
GOTO :EOF
:; }

:menu_2 (){
ECHO. Пункт второй
GOTO :EOF
:; }

:;#start

SET /p opt=Введите цифру:
CALL :menu_%opt%&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Мой оппонент признал, что в споре я победил.&lt;/p&gt;
</description>
</item>


</channel>
</rss>