標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

n番煎じの powershell 向け shebang !

PowerShellスクリプトを実行しようとすると、実行ポリシーを一時的に変えるため

powershell -ExecutionPolicy RemoteSigned -file HOGE.ps1

と長い起動コマンドラインになってしまいがちです。

ということで、HOGE.ps1 を実行するために、別途 HOGE.cmd などのバッチファイルを用意したりしますが、ファイルが増えると、配布する手間、説明する手間(「二つを同じフォルダーに置いて実行してください!」とか)も増え、しかも使ってもらえなくなったりして、なかなか難儀です。

そこでバッチファイルの中に PowerShell スクリプトを組み込んで、1ファイル化しようということになります。過去、PowerShell をバッチファイルに組み込む方法は、過去いろいろ記事がありました。

そんな中、先日見つけた記事:

これはかなり決定打のように思われました。echo がバッチコマンド、PowerShell 両方にある(echo = Write-Outputエイリアス)ことを利用するなど、よく考えられたものです。ただ、惜しまれるのはバッチとの共用行が長くて、覚えづらいということでしょうか。

そこで、自分なりにアレンジしてみました。

@set "args=%*"
@powershell "iex((@('')*3+(cat '%~f0'|select -skip 3))-join[char]10)"
@exit /b %ERRORLEVEL%

Write-Output $env:args

# vim:set ft=ps1:
  • プログラム的にバッチファイルの先頭3行を空行に差し替えて、Invoke-Expression(iex)で処理するようにした(3行を差し替えずに、そのまま消してもよかったのだが、エラーの行番号がズレさせたくなかったため)
  • 改行を表す「\"`n\"」を [char]10 などに置き換えた(エディターのシンタックスハイライトに優しい)
  • 引数は残念ながら $args[] ではなく、$env:args を使う(とほほ)

しかし、こういうのも、多分、どこかで既出なんでしょうね

GoのEnum表現は、go:generate + stringer の出番?

主旨が違うかもしれないんだけど、そういうのは go:generate + stringer の出番と違うかなと思った。

package main

import "fmt"

type SkuNameEnum int

//go:generate stringer -type=SkuNameEnum

const (
    Free SkuNameEnum = iota
    PerNode
    Premium
    Standalone
    Standard
    Unlimited
)

func main(){
    for i := Free ; i <= Unlimited ; i++ {
        fmt.Println( i )
    }
}
$ go generate
$ go build
$ ls
foo.go                skuname.exe*          skunameenum_string.go
$ skuname.exe
Free
PerNode
Premium
Standalone
Standard
Unlimited
$

PowerShell での項目の並べ方整理

コマンドを書く場合は「空白」で区切る。

  • 関数・外部コマンドに関係なく、引数リスト全体を囲む「括弧」は要らない。
Write-Verbose -Message "$ go build"

コマンドの引数に「式」を書いて評価させる場合は、その引数1個を「括弧」で囲む

  • 囲まないと、個別のトークンがそのまま、コマンドに渡される
Write-Verbose -Message ("Found {0}" -f $exename)

配列リテラルは要素をカンマで区切る

        foreach( $p in @(`
            "nyagos.exe",`
            "nyagos.syso",`
            "version.now",`
            "mains\bindata.go",`
            "goversioninfo.exe",`
            "go-bindata.exe" ) )
        {

※ 逆クォート一つは、行継続用のエスケープ文字

連想配列リテラルは代入式をセミコロンで連結する

PowerShell連想配列(ハッシュ)によると「@{one = 1; two = 2; three = 3}」と書けばよいらしい(手抜き)

ファイルサーバ名が変わったので、リンク切れした「お気に入り」を修復

Windows7エクスプローラで出てくる「お気に入り」は、%USERPROFILE%\Links というフォルダーにショートカットが全部並んでいる。 PowerShell を使って全部リネームしよう、そうしよう。

$fromName = "\\旧サーバ名\"
$toName = "\\新サーバ名\"

$wsh = New-Object -ComObject WScript.Shell
Get-ChildItem (Join-Path $env:userprofile "Links") | %{
    if( $_.Extension -eq ".lnk" ){
        $shortcut = $wsh.CreateShortcut($_.FullName)
        $target = $shortcut.TargetPath
        if( $target.StartsWith($fromName) ){
            $newTarget = $toName + $target.substring($fromName.Length)
            if( Test-Path $newTarget ){
                Write-Output ("    " + $target)
                Write-Output ("--> " + $newTarget)
                $shortcut.TargetPath = $newTarget
                $shortcut.Save()
            }
        }
    }
}

Write-Host と Write-Output ではリダイレクトした時の改行コードが変わってしまう

(※ 2017.09.15追記: stknohg さんから、いろいろ技術的な補足をいただきました。詳しくはコメント欄を参照ください)

  • Write-Host の出力を、PowerShell の呼び出し元でリダイレクトすると、改行コードが LF になる。
  • CRLF にするには Write-Output を使わねばならない

host.ps1:

Write-Host "A"
Write-Host "B"
$ host.ps1 > host.txt
$ dumphead.exe host.txt
41 0A 42 0A

(※ 9/15 追記。NYAGOS から実行したので省略していますが、これ頭に powershell -ExecutionPolicy RemoteSigned -file が入っています)

output.ps1:

Write-Output "A"
Write-Output "B"
$ output.ps1 > output.txt
$ dumphead.exe output.txt
41 0D 0A 42 0D 0A

(※ 9/15 追記。同じく頭に powershell -ExecutionPolicy RemoteSigned -file が入っています)

最初、vim とかで見てたので気づきませんでしたが、 プログラムを提出後、メモ帳で見たら Write-Host で出したプログラムの改行が消えて見えたので、 慌ててしまいました。


Write-HostとWrite-Outputの違い - しばたテックブログ を読みました。本来は:

  • Write-Host はコンソールに出力するコマンド
  • Write-Output は標準出力に出力するコマンド

で、その辺の違いが出力に現れているのかもしれませんね(おざなり)

以上

OneNote 2016 を使ってみた

evernote のデータを本格的に移行しようとは思わなかったが、ちょっとお試しで OneNote を使ってみた。

OneNote の種類にとまどった

会社PC の Office2010 に入ってる OneNote と、Windows10 に最初から入っている OneNote のユーザインターフェイスが微妙に違う。 単にバージョンが違うからかと思って、フリー版の最新であるはずの OneNote を入れてみたが、それでもなお Windows10 の OneNote とそっくりにならない。

どうやら、Windows10 にプリインストールされている「OneNote(無印)」はモバイル向けの OneNote で、 フリー版の OneNote =「OneNote 2016」とはまた違うバージョンとは違うもののようだ。 使い勝手では Office 2016 が一番使いやすいので、会社PC、自宅PCともに OneNote 2016 に統一することにした。

なお、OneNote 2010 もちょっと使ってデータがあったのだが、OneNote 2016 にデータを移すには、 サブスクリプションの購入が必要のようだ。大したデータでもないので、すっぱり捨てた。

evernote との違い

  • OneNote は端末数に制限なし。evernote は無料は2台まで

    • OneNote はパスワード付きのフォルダーが作れるが、PC でしか読むことができない。モバイル端末ではパスワードを知っていてもフォルダーを開けない(※@unagix さんからの指摘を受けて追記:開けないのは、Fireタブレットだけかも?)
  • OneNote はフォルダー管理、evernote はタグ管理

    • OneNote は多層の階層構造がある
      • ノートブック (>セクショングループ)>セクション>ページ>サブページ
      • サブページの下にサブページは作れるが、折りたたむ事ができないので、あまり意味がない
    • evernote にも階層構造はあるが、ノートブック>ノートと1階層のみ
      • そのかわり、多数のタグをつけることができる
  • 両方とも検索すれば、まぁ、記述を探すことには不便がない。

  • ゴミページがたくさんできてしまうような使い方の場合、階層構造がある方がゴミページを目立たない方へ移動させられるということで、精神衛生上よいような気がする(そんなことしても「検索」すれば出てきてしまうのではあるが)

その他

  • OneNote はオフィスファミリーの一員であるため、Excel とかと相性がよいような気がする。
  • OneNote にはファイルを添付できるのはよいのだが、ファイル名を全部表示してくれないのは非常に残念。拡張子を表示してくれないのはだめだろう。
  • 自分は、自分のマシンのファイル散乱対策に結構使えると思ってる。Thunderbird のメールを直接ドラッグ&ドロップも出来るので、メール+関連ファイルを1つのページにおさめておくことができる(evernote でも多分できるだろう)。

とりあえず、以上

本当に Windows10 のコマンドプロンプトはエスケープシーケンスをサポートしたのか?

したっぽい。実験してみよう

package main

import (
    "fmt"
    "os"
    "syscall"
    "unsafe"
)

var kernel32 = syscall.NewLazyDLL("kernel32")

const STD_INPUT_HANDLE = uintptr(1) + ^uintptr(10)
const STD_OUTPUT_HANDLE = uintptr(1) + ^uintptr(11)
const STD_ERROR_HANDLE = uintptr(1) + ^uintptr(12)
const ENABLE_VIRTUAL_TERMINAL_PROCESSING uintptr = 0x0004

var procGetStdHandle = kernel32.NewProc("GetStdHandle")
var procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
var procSetConsoleMode = kernel32.NewProc("SetConsoleMode")

func Main() error {
    var mode uintptr
    console, _, _ := procGetStdHandle.Call(STD_OUTPUT_HANDLE)

    rc, _, err := procGetConsoleMode.Call(console, uintptr(unsafe.Pointer(&mode)))
    if rc == 0 {
        return err
    }
    defer procSetConsoleMode.Call(console, mode)

    rc, _, err = procSetConsoleMode.Call(console, mode|ENABLE_VIRTUAL_TERMINAL_PROCESSING)
    if rc == 0 {
        return err
    }
    println("\x1B[32;1mAHAHA\x1B[37;1m")
    return nil
}

func main() {
    if err := Main(); err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(1)
    } else {
        os.Exit(0)
    }
}

image.png

SetConsoleMode で、ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x0004) というビットを立てなくてはいけないようです。

くわしい仕様はこちら:

Go でやる分には go-colorable があるので、あんまりすぐ使う必要性はありませんが、いつが Windows 7/8 がサポート切れになった時、使う日が来るかもしれません。