echo出力について~コマンドプロンプト(Windows、DOS窓、command)~

echo出力について~コマンドプロンプト(WindowsDOS窓、command)~

経緯

今回ハマったのはMD5を実装するに当たって、動作確認のためにコマンドプロンプト上でMD5動作チェックを行っているとき起きた。

>echo hello world |openssl md5
(stdin)= d1b9c5009a6ddd7dacb45eddb78fa23a

hello world の 文字列をMD5ハッシュ表示したものである。

作った関数を使用すると5eb63bbbe01eeed093cb22bb8f5acdc3となる。 はて?どうなっていうんだとデバックに励むのだが一向に解決しない。

スペースか!

>echo hello world| openssl md5
(stdin)= a0f2a3c1dcd5b1cac71bf0c03f2ff1bd

いや、テキストファイルを使って実行

>type hw
hello world
>openssl md5 hw
MD5(hw)= 5eb63bbbe01eeed093cb22bb8f5acdc3

えーーー、ということでecho -nを思い出したのでbash

$echo -n hello world | openssl md5
(stdin)= 5eb63bbbe01eeed093cb22bb8f5acdc3
$echo -n hello world| openssl md5
(stdin)= 5eb63bbbe01eeed093cb22bb8f5acdc3

bashでは、パイプの前のスペースは無効化されるらしい。

>type hw |openssl md5
(stdin)= 5eb63bbbe01eeed093cb22bb8f5acdc3

結論:echoコマンドは、画面表示用のコマンドでるので改行コードが追加される。文字入力してそれをパイプで使うためのものではない

今回の記事はコマンドプロンプトでの入力文字列について扱う

cmd.exe echoのヘルプ

>echo /?
メッセージを表示したり、コマンド エコーの ON と OFF を切り替えます。

  ECHO [ON | OFF]
  ECHO [メッセージ]

現在のエコー設定を表示するには、パラメーターを指定せずに ECHO と入力してください。

bash echoのヘルプ

$help echo
echo: echo [-neE] [arg ...]
    Write arguments to the standard output.

    Display the ARGs, separated by a single space character and followed by a
    newline, on the standard output.

    Options:
      -n        do not append a newline
      -e        enable interpretation of the following backslash escapes
      -E        explicitly suppress interpretation of backslash escapes

    `echo' interprets the following backslash-escaped characters:
      \a        alert (bell)
      \b        backspace
      \c        suppress further output
      \e        escape character
      \E        escape character
      \f        form feed
      \n        new line
      \r        carriage return
      \t        horizontal tab
      \v        vertical tab
      \\        backslash
      \0nnn     the character whose ASCII code is NNN (octal).  NNN can be
                0 to 3 octal digits
      \xHH      the eight-bit character whose value is HH (hexadecimal).  HH
                can be one or two hex digits

    Exit Status:
    Returns success unless a write error occurs.

WindowsCertUtil -dumpコマンドを使ってみる

最近のWindowsには、ファイル中身(ダンプリスト)を見るコマンドがないのでCertUtilコマンドで代用する。 CertUtilコマンド自体は、デジタル証明書等のツールで色々できる。

>echo hello world > hw2
>certutil -dump hw2
    85 e9 65 a3 0a 2b 95                               ..e..+.
CertUtil: -dump コマンドは正常に完了しました。

>echo hello world > hw3
>certutil -dump hw3
    85 e9 65 a3 0a 2b 95                               ..e..+.
CertUtil: -dump コマンドは正常に完了しました。

>dir
2020/12/18  10:25                11 hw
2020/12/18  10:58                14 hw2
2020/12/18  10:58                13 hw3

一部省略するけど

はい、使い物になりませんでした。また、罠にかかるところだったよ!

>certutil -dump -?
使用法:
  CertUtil [オプション] [-dump]
  CertUtil [オプション] [-dump] [ファイル]
  構成情報またはファイルをダンプします

オプション:
  -f                -- 強制的に上書きします
  -user             -- HKEY_CURRENT_USER キーまたは証明書ストアを使用します
  -Unicode          -- リダイレクトされた出力を Unicode として書き込む
  -gmt              -- 時刻を GMT で表示します
  -seconds          -- 時間を秒とミリ秒で表示します
  -Silent           -- (-q) サイレント フラグを使って暗号コンテキストを取得します
  -split            -- 埋め込まれた ASN.1 要素を分割し、ファイルに保存します
  -v                -- メッセージを詳細に表示します
  -privatekey       -- パスワードと秘密キーのデータを表示します
  -pin PIN                  -- スマート カードの PIN
  -p パスワード             -- パスワード
  -t タイムアウト           -- URL のフェッチのタイムアウト (ミリ秒)
  -sid WELL_KNOWN_SID_TYPE  -- 数値 SID
            22 -- ローカル システム
            23 -- ローカル サービス
            24 -- ネットワーク サービス

CertUtil -?              -- 動詞の一覧 (コマンドの一覧) を表示します
CertUtil -dump -?        -- "dump" 動詞のヘルプ テキストを表示します
CertUtil -v -?           -- すべての動詞のヘルプ テキストをすべて表示します

"構成情報またはファイルをダンプします" つまりこれは、構成情報が出力されていてファイルをダンプしてないわけだ。使えない。というかオプションで選べるようにしておけよ!

CertUtilを調べているうちにMD5も表示できることが分かった

CertUtil を使って MD5を表示する

MD5ハッシュで表示したいファイルを用意して

>type hw
hello world
>certutil -hashfile  hw MD5
MD5 ハッシュ (対象 hw):
5eb63bbbe01eeed093cb22bb8f5acdc3
CertUtil: -hashfile コマンドは正常に完了しました。

opensslのMD5と同じ結果になります。

CertUtil を使って ダンプリストを表示する

まあ、上で使えないー。と言っていたんだが、色々と調べてみると隠しコマンドcertutil -uSAGEで使えることが分かったので

>certutil -encodehex -?
使用法:
  CertUtil [オプション] -encodehex InFile OutFile [type]
  ファイルを 16 進数でエンコードします

オプション:
  -f                -- 強制的に上書きします
  -Unicode          -- リダイレクトされた出力を Unicode として書き込む
  -UnicodeText      -- 出力ファイルを Unicode で書き込む
  -gmt              -- 時刻を GMT で表示します
  -seconds          -- 時間を秒とミリ秒で表示します
  -v                -- メッセージを詳細に表示します
  -privatekey       -- パスワードと秘密キーのデータを表示します
  -pin PIN                  -- スマート カードの PIN
  -sid WELL_KNOWN_SID_TYPE  -- 数値 SID
            22 -- ローカル システム
            23 -- ローカル サービス
            24 -- ネットワーク サービス

CertUtil -?              -- 動詞の一覧 (コマンドの一覧) を表示します
CertUtil -encodehex -?   -- "encodehex" 動詞のヘルプ テキストを表示します
CertUtil -v -?           -- すべての動詞のヘルプ テキストをすべて表示します

このコマンドダンプリストをファイル化することはできるが表示は直接できないっぽいのでいったんファイルにしたものを表示させるとこうなる。

>echo hello world>temp

>CertUtil -encodehex temp temp.txt
入力長 = 13
出力長 = 71
CertUtil: -encodehex コマンドは正常に完了しました。

>type temp.txt
0000    68 65 6c 6c 6f 20 77 6f  72 6c 64 0d 0a            hello world..

>del temp temp.txt

>

ということになります。つまり、echoコマンドは、メッセージを追加したあとに0x0d,0x0aの改行コードを追加して吐き出していたということになります。UNIX系のshellコマンドのechoには-nオプションがあって改行なしになるのですがWindowsのechoにはこのオプションがないのでパイプを使った入力は出来ません。

>CertUtil -encodehex hw hw.txt
入力長 = 11
出力長 = 69
CertUtil: -encodehex コマンドは正常に完了しました。

>CertUtil -encodehex hw2 hw2.txt
入力長 = 14
出力長 = 72
CertUtil: -encodehex コマンドは正常に完了しました。

>CertUtil -encodehex hw3 hw3.txt
入力長 = 13
出力長 = 71
CertUtil: -encodehex コマンドは正常に完了しました。

>type hw.txt
0000    68 65 6c 6c 6f 20 77 6f  72 6c 64                  hello world

>type hw2.txt
0000    68 65 6c 6c 6f 20 77 6f  72 6c 64 20 0d 0a         hello world ..

>type hw3.txt
0000    68 65 6c 6c 6f 20 77 6f  72 6c 64 0d 0a            hello world..

hwは改行なし
hw2は、echo hello world >hw2でリダイレクトやパイプの前にスペースが入っている場合。
hw3は、echo hello world>hw3でリダイレクトやパイプの前にスペースが無い場合。
となります。

echo(bash)の動作

$echo hello world|od -tx1c
0000000  68  65  6c  6c  6f  20  77  6f  72  6c  64  0a
          h   e   l   l   o       w   o   r   l   d  \n
0000014
$echo -n hello world|od -tx1c
0000000  68  65  6c  6c  6f  20  77  6f  72  6c  64
          h   e   l   l   o       w   o   r   l   d
0000013
$echo -n hello world |od -tx1c
0000000  68  65  6c  6c  6f  20  77  6f  72  6c  64
          h   e   l   l   o       w   o   r   l   d
0000013
$echo '  hello world ' |od -tx1c
0000000  20  20  68  65  6c  6c  6f  20  77  6f  72  6c  64  20  0a
                  h   e   l   l   o       w   o   r   l   d      \n
0000017
$echo "  hello world " |od -tx1c
0000000  20  20  68  65  6c  6c  6f  20  77  6f  72  6c  64  20  0a
                  h   e   l   l   o       w   o   r   l   d      \n
0000017
$

echoコマンドは、文字で始まって文字で終わる文字列の頭と終わりのスペースは削除される。文字列の終わりに0x0a改行コードを追加される。'~'シングルクォーテーションで囲むとその間の文字列が表示される。これによって、頭と終わりにスペースをいくつでも追加できる。"~"ダブルクォーテーションは、ダブルクォーテーションも出力される。

echo(cmd.exe)の動作

"~"で囲んだ場合そのままという感じになるかといって特別意味がない文字という扱いでもないみたいで、"""と奇数の扱いにすると|(パイプ)処理が無効になるので特殊処理のキャンセルとしては動作しているみたい。

>echo "hello world" | od -t x1c
0000000  22  68  65  6c  6c  6f  20  77  6f  72  6c  64  22  20  0d  0a
          "   h   e   l   l   o       w   o   r   l   d   "      \r  \n
0000020

>echo   " " " "  | od -t x1c
0000000  20  20  22  20  22  20  22  20  22  20  20  0d  0a
                  "       "       "       "          \r  \n
0000015

>echo   " " "  | od -t x1c
  " " "  | od -t x1c

>

また、echoの後記号等をいれると無効になり表示状は改行のみ表示された形になる。 これは、batchファイルとうでechoを複数行にわたって使う場の改行のみの行に使用されることがあるみたいだ。

>echo. | od -t x1c
0000000  20  0d  0a
             \r  \n
0000003

>echo/ | od -t x1c
0000000  20  0d  0a
             \r  \n
0000003

>echo\ | od -t x1c
0000000  20  0d  0a
             \r  \n
0000003

>echo.| od -t x1c
0000000  0d  0a
         \r  \n
0000002

echo改行なし

コマンドプロンプトechoコマンドを使っては、出来ないので変わりの方法?
まあ、テキストエディタを使って入力文章を作りtype filename|openssl md5とかで行うかもしくは、 openssl md5 filenameパラメータでファイルを指定する方法がある。他にもSET /Pを使う方法が知られている

>set /?
cmd.exe 環境変数を表示、設定、または削除します。

SET [変数名=[文字列]]

  変数名   環境変数名を指定します。
  文字列   変数に割り当てる文字列を指定します。

現在の環境変数を表示するには、パラメーターを指定せずに SET と入力してください。

コマンド拡張機能を有効にすると、SET は、次のように変更されます:

等号や値を指定せずに、変数名だけを指定して SET コマンドを実行すると、
SET コマンドに指定された名前にプレフィックスが一致するすべての変数の値が
表示されます。たとえば、

    SET P

と入力すると、文字 'P' で始まるすべての変数が表示されます。

変数名が現在の環境に見つからない場合は、SET コマンドは、ERRORLEVEL を
1 に設定します。

SET コマンドでは、変数の名前に等符号を使用することはできません。

SET コマンドには、2 つの新しいスイッチが追加されています:

    SET /A 式
    SET /P 変数=[プロンプト文字列]

/A スイッチは、等号の右側の文字列が、評価する数式であることを
指定します。式の評価はごく単純で、次の操作がサポートされます。
操作は、優先順位の高い順に示されています:

    ()                  - グループ化
    ! ~ -               - 単項演算子
    * / %              - 算術演算子
    + -                 - 算術演算子
    << >>               - 論理シフト
    &                    - ビット演算子 AND
    ^                   - ビット演算子排他的 OR
    |                   - ビット演算子 OR
    = *= /= %= += -=   - 代入
      &= ^= |= <<= >>=
    ,                   - 式の区切り記号

論理演算子またはモジュール演算子を使う場合は、式文字列を
引用符で囲む必要があります。式内の数値以外の文字列は環境変数文字列として
処理され、使用される前に数値に変換されます。
指定された環境変数名が現在の環境で定義されていない場合は、
値として 0 が使用されます。
これにより、いくつもの % 記号を入力して値を取得しないでも、
環境変数の値を算術演算に使うことができます。
コマンド スクリプト外でコマンド ラインから SET /A を実行すると、
式の最終的な値が表示されます。
割り当て演算子を使うには、割り当て演算子
の左側に環境変数名が必要です。
数値は 10 進数ですが、プレフィックスとして 0x
を付けると 16 進数、0 を付けると 8 進数になります。従って、0x12 は 18、
あるいは 022 と同じです。
8 進表記を使う場合は、注意してください。08 や
09 は、8 と 9 が有効な 8 進数ではないため、
有効な数値ではありません。

/P はユーザーによって入力された入力行を変数の値として設定できるようにします。
入力行を読み取る前に、指定されたプロンプト文字列を表示します。プロンプト文
字列は空でもかまいません。

環境変数の置換は、次のように拡張されます:

    %PATH:文字列 1 = 文字列 2%

は、PATH 環境変数を展開し、その結果に含まれるすべての "文字列 1" を
"文字列 2" に置き換えます。
"文字列 2" に空の文字列を指定すると、展開された出力からすべての "文字列 1"
を削除することができます。
"文字列 1"
をアスタリスクで始め、展開された出力の先頭から、文字列 1 の残りの部分
が最初に現れるまでのすべてを一致させることもできます。

また、展開の副文字列を指定することもできます。

    %PATH:~10,5%

は、PATH 環境変数を展開し、展開結果の 11 番目 (オフセット 10) の文字
から始まる 5 文字だけを使います。長さが指定されなかった場合は、変数の
値の残りの長さを既定値とします。オフセットまたは長さのどちらかが負の値
の場合、環境変数の値の長さに指定されたオフセットまたは長さをたしてその
数を使います。

    %PATH:~-10%

は、パス変数の最後の 10 文字が展開されます。

    %PATH:~0,-2%

は最後の 2 文字以外のすべてが展開されます。

最後に、遅延環境変数の展開が追加されました。このサポートは常に既定で
無効になっていますが、CMD.EXE の /V: のコマンド ライン スイッチを使
って有効または無効にできます。
CMD /? を参照してください。

遅延環境変数の展開は、実行時ではなく、テキスト行を読み取るときに展開
されるという現在の制限を避けるために役立ちます。
次の例は即時変数展開の問題を説明しています。

    set VAR=before
    if "%VAR%" == "before" (
        set VAR=after;
        if "%VAR%" == "after" @echo If you see this, it worked
    )

この例は、論理的には IF 文が別の IF 文の本体に含まれる複合文なので、
両方の IF 文の %VAR% が、最初の IF 文を読み取ったときに展開されます。
このため、メッセージは決して表示されません。
複合文の中の IF では "before" と "after" が比較され、
決して等しくはなりません。
同様に次の例も期待したようには動作しません。

    set LIST=
    for %i in (*) do set LIST=%LIST% %i
    echo %LIST%

この例では、現在のディレクトリのファイルの一覧は作成されず、代わりに最後
に見つけられたファイルが LIST 変数に設定されます。
これは %LIST% が FOR 文が読み取られるとき、
一度だけ展開され、そのときは LIST 変数が空だからです。
つまり、実際に実行されている FOR ループは

    for %i in (*) do set LIST= %i

で、LIST に最後に見つけられたファイルを設定し続けているだけです。

遅延環境変数の展開では、実行時に環境変数を展開するために異なった文字
(感嘆符) を使うことができます。
遅延環境変数の展開が有効な場合、上記の
例は次のように書くと意図したように動作します。

    set VAR=before
    if "%VAR%" == "before" (
        set VAR=after
        if "!VAR!" == "after" @echo If you see this, it worked
    )

    set LIST=
    for %i in (*) do set LIST=!LIST! %i
    echo %LIST%

コマンド拡張機能が有効な場合、SET によって表示される変数の一覧には
現れないいくつかの動的な環境変数があります。
これらの変数の値は、変数の値が展開されるときに
動的に計算されます。
ユーザーがこれらの名前の変数を明示的に定義する場合、
その定義は下記の動的な定義を無効にします。

%CD%            - 現在のディレクトリ文字列に展開します。

%DATE%          - DATE コマンドと同じフォーマットで現在の日付に展開します。

%TIME%          - TIME コマンドと同じフォーマットで現在の時刻に展開します。

%RANDOM%        - 0 から 32767 の間の任意の 10 進数に展開します。

%ERRORLEVEL%    - 現在の ERRORLEVEL の値に展開します。

%CMDEXTVERSION% - 現在のコマンド プロセッサ拡張機能のバージョン番号に
                    展開します。

%CMDCMDLINE%    - コマンド プロセッサを起動したオリジナル コマンド ライン
                    に展開します。

%HIGHESTNUMANODENUMBER%
                  - このコンピューター上の最大の NUMA ノード番号に展開します。

>

要点だけだと

SET /P 変数=[プロンプト文字列]

/P はユーザーによって入力された入力行を変数の値として設定できるようにします。
入力行を読み取る前に、指定されたプロンプト文字列を表示します。プロンプト文
字列は空でもかまいません。

ユーザーによって入力された入力行を変数の値として設定できる つまり、ですが、変数のパラメータを省略できたりします。その場合変数代入がなくなり

>set /P a=
入力データ

>set a
a=入力データ
省略
>

set /P a=で入力待ちになり入力データ+改行で入力終了して環境変数aに入力データが代入れた。という動作になる。この時入力データに改行コードが入ると使いにくいので含まれない。これを応用したのが

>set /P=hello world<nul|od -t x1c
0000000  68  65  6c  6c  6f  20  77  6f  72  6c  64  20
          h   e   l   l   o       w   o   r   l   d
0000014

>set /P="hello world"<nul|od -t x1c
0000000  68  65  6c  6c  6f  20  77  6f  72  6c  64
          h   e   l   l   o       w   o   r   l   d
0000013

>set /P= "hello world" <nul|od -t x1c
0000000  68  65  6c  6c  6f  20  77  6f  72  6c  64
          h   e   l   l   o       w   o   r   l   d
0000013

>set /P='hello world'<nul|od -t x1c
0000000  27  68  65  6c  6c  6f  20  77  6f  72  6c  64  27  20
          '   h   e   l   l   o       w   o   r   l   d   '
0000016

となります。 echoと逆で'~'シングルクォーテーションで囲むと。シングルクォーテーションは出力される。また、"~"ダブルクォーテーションは、代入文字列として扱われ囲んだ範囲のみを文字列としてあつかってくれる。 なにもないとスペースが入るこれ変数にはいると使いにくだろうとおもうのだが。また、<nulを付けないと入力待ち状態でとまるのでリダイレクトで入力無を入力している。<空のファイル名でもいいが意味がないのでNULを使ったほうがよい

まとめ hello worldの入力

当初行いたかったhello worldは、

cmd.exeでは、

>set /P= "hello world" <nul|openssl md5
(stdin)= 5eb63bbbe01eeed093cb22bb8f5acdc3

bashでは、

$echo -n hello world |openssl md5
(stdin)= 5eb63bbbe01eeed093cb22bb8f5acdc3

とコマンド入力で確認できる。