標準愚痴出力

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

ここ近年、Go で作った UNIX っぽい Windows のツール

ANSI/UTF8自動判別と書いているは行単位で判断しているので、混在しててもだいたい大丈夫

神Excel にテキストファイルを流し込むスクリプト書いたよー

(2019.04.19追記) Goで書き直した最新版は こちら


godexcel.cmd C3 AA30 tmp.xls ./GodExcel.cmd

という感じで実行すると、神 Excel「tmp.xls」の C3~AA30 の領域に、自分自身(GodExcel.cmd) の内容を展開して

f:id:zetamatta:20171123225106p:plain

という感じになる。

GodExcel.cmd

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

$std_cell_width = 1.50

function Split-LikeShell($s){
    $rx = [regex]'"[^"]*"'
    while( $true ){
        $m = $rx.Match($s)
        if( -not $m.Success ){
            break
        }
        $left = $s.SubString(0,$m.Index)
        $right = $s.SubString($m.Index+$m.Length)
        $mid = (($m.Value -replace " ",[char]1) -replace '"','')
        $s = $left + $mid + $right
    }
    ($s -split " ") | ForEach-Object{ $_ -replace [char]1," " }
}

$args = (Split-LikeShell $env:args)

function Conv-RC($str){
    $i = 0
    $str = $str.ToUpper()
    $col = 0
    while( $i -lt $str.Length ){
        $index = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".IndexOf($str.substring($i,1))
        if( $index -lt 0 ){
            break
        }
        $col = $col * 26 + ($index+1)
        $i++
    }
    $row = 0
    while( $i -lt $str.Length ){
        $index = "0123456789".IndexOf($str.substring($i,1))
        if( $index -lt 0 ){
            break
        }
        $row = $row * 10 + $index
        $i++
    }
    return @($row,$col)
}

$left = 1
$top = 3
$right = 200
$bottom = 200

if( $args.Length -lt 2 ){
    Write-Output `
        "Usage: godexcel.ps1 [LeftTop] [RightBottom] ExcelFile TextFile..."
    exit
}

if( $args.Length -ge 1 -and $args[0] -match "^[A-Za-z]+[0-9]+$" ){
    $rc = Conv-RC($args[0])
    $top = $rc[0]
    $left = $rc[1]
    $args = $args[1..($args.Length-1)]
    if( $args.Length -ge 1 -and $args[0] -match "^[A-Za-z]+[0-9]+$" ){
        $rc = Conv-RC($args[0])
        $bottom = $rc[0]
        $right = $rc[1]
        $args = $args[1..($args.Length-1)]
    }
}

$excel = New-Object -ComObject Excel.Application
$cellwidth = @{}

try{
    $excel.Visible = $true
    
    $filename = [System.IO.Path]::GetFullPath($args[0])
    [System.Console]::WriteLine("Excel=$filename")
    if( Test-Path $args[0] ){
        $book = $excel.WorkBooks.Open($filename)
        $new = $false
    }else{
        $book = $excel.WorkBooks.Add()
        $new = $true
    }
    $sheet = $book.ActiveSheet

    $row = $top
    for($i = 1 ; $i -lt $args.Length ; $i++){
        Get-Content $args[1] |
        ForEach-Object {
            if( $row -le $bottom ){
                $count = $_.Length
                $col = $left
                for($j=0 ; $j -lt $count ; $j++){
                    if( $col -gt $right ){
                        $col = $left
                        $row++
                    }
                    if( $new -and -not $cellwidth.ContainsKey($col) ){
                        $sheet.Columns($col).ColumnWidth = $std_cell_width
                        $cellwidth[$col] = $true
                    }
                    $sheet.Cells.Item($row,$col) = $_.substring($j,1)
                    $col++
                }
                while( $col -le $right ){
                    $sheet.Cells.Item($row,$col) = ""
                    $col++
                }
                $row++
            }
        }
    }
    if( $new ){
        $book.SaveAs($filename)
    }
}finally{
    $excel.Quit()
}

# vim:set ft=ps1:

PowerShell で、二つのフォルダーのファイル構成を比較するコマンドを作る

dirdiff.cmd

@set "arg1=%~1" & set "arg2=%~2"
@powershell "iex((@('','','')+(cat '%~f0' | select -skip 3))-join[char]10)"
@exit /b %ERRORLEVEL%

$md5 = New-Object System.Security.Cryptography.MD5CryptoServiceProvider

function Get-MD5s($dir){
    $md5s = @{}
    Get-ChildItem $dir |
    Where-Object { $_.Mode[0] -ne "d" } |
    ForEach-Object {
        $data = [System.IO.File]::ReadAllBytes($_.FullName)
        $bs = $md5.ComputeHash($data)
        $md5s[ $_.Name ] = `
            ([System.BitConverter]::ToString($bs).ToLower() -replace "-","")
    }
    return $md5s
}

$dir1 = $env:arg1
$dir2 = $env:arg2

$hash1 = (Get-MD5s $dir1)
$hash2 = (Get-MD5s $dir2)

foreach($p in $hash1.Keys){
    if( $hash2.ContainsKey($p) ){
        $private:val1 = $hash1[$p]
        $private:val2 = $hash2[$p]
        if( $val1 -ne $val2 ){
            Write-Output ("{0} differ" -f $p)
            Write-Output ("  {0} {1}" -f $val1,(Join-Path $dir1 $p))
            Write-Output ("  {0} {1}" -f $val2,(Join-Path $dir2 $p))
        }else{
            Write-Verbose ("{0} same" -f $p)
        }
    }else{
        Write-Output ("{0} not found" -f (Join-Path $dir2 $p))
    }
}

foreach($p in $hash2.Keys){
    if( -not $hash1.ContainsKey($p) ){
        Write-Output ("{0} not found" -f (Join-Path $dir1 $p))
    }
}

# vim:set ft=ps1:
  • 最初の3行は、PowerShell をバッチファイル化するためのまじない
  • 二つのディレクトリの直下にあるファイルの md5連想配列に格納して、比較するだけ

こういうのは別のバッチファイルから呼び出したいから、Goで作った方がいいな…と後から思いました。まる

追記

Goで作ってみました。https://github.com/zetamatta/experimental/blob/master/dirdiff/main.go

(補足)git で特定の2つのフォルダーを使ってる履歴のみ残して、後は消す

最初の commit だけは argf , seek フォルダーに関わってなくとも残ってしまう(あとで revert した)

git rebase -i -root で、最初の commit も削除できた。

git で特定の2つのフォルダーを使ってる履歴のみ残して、後は消す

フォルダー一つだけなら git filter-branch --subdirectory-filter が使えるが、複数の場合はそうもいかない。 複数のフォルダーだけを残すために、Lua スクリプトをさくっと書いた。

function getgitlog(files)
    local fd = io.popen("git log " .. table.concat(files," ") )
    local commit = {}
    if fd then
        for line in fd:lines() do
            if string.match(line,"^commit ") then
                commit[ #commit + 1 ] = string.sub(line,8)
                print( commit[ #commit ])
            end
        end
        fd:close()
    end
    return commit
end


local commit = getgitlog( {"argf","seek"} )
local all = getgitlog( {} )
local first = all[ #all ]

os.execute("git checkout " .. first)
os.execute("git branch tmp")
os.execute("git checkout tmp")
for i=#commit,1,-1 do
    os.execute("git cherry-pick "..commit[i])
end

自分の都合だけで作ったので、下記のような制限がある。

  • 1回限りの利用なので、残すフォルダー「argf」「seek」は決め打ち
  • argf , seek フォルダーに関わっている commit だけをcherry-pick するという方式なので、厳密には argf , seek だけを抽出するわけではない
  • 最初の commit だけは argf , seek フォルダーに関わってなくとも残ってしまう(あとで revert した)
  • git filter-branch --subdirectory-filter みたいに、フォルダーの解消まではしない(二つフォルダーがあるから実際無理)。
  • フォルダー名に空白が含まれると多分誤動作

これを使って公開したのが、先に公開した seek コマンドだったりする。

うっかり、オレオレ grep (seek)を書いてしまった。

先日、while( <> ){ } 的なものの Go 版ライブラリ argfを書いてみたわけだけれども、そのサンプルとして 簡単な grep (seek)を書いてみた。

最初は必要最小限にしていたんだけど、jvgrep みたいに「検索文字列の部分をハイライト表示」できないかなと思って頑張ってみたら、Go言語のライブラリが充実していることもあって、案外簡単に出来てしまった。

で、調子にのって、自分が grep に望む機能を実装してみた。

  • UTF8 と ANSI(現在のコードページマルチバイト文字列:日本なら SJIS~CP932)の行単位での簡易自動判定
    • UTF8 と解釈できなければ、ANSI と判断するという安直判定
    • 前は ShiftJIS の時は Windows 標準の findstr.exe、UTF8 なら jvgrep を使っていたが、いちいち使い分けるのは面倒くさかった。
    • jvgrep はテキストファイルチェックが厳しくて、すぐバイナリファイル扱いにしてしまう。
    • git で SJIS テキストの差分を見る時、git の UTF8 と、本文の SJIS が混ざってしまう。両方とも化けずに見たい場合があるので、行単位で別々の文字コード判定にしたかった。

本当に自分が必要とした機能しか入れていないので、以下のような制限がある

  • オプションは -i (大文字・小文字を区別しない)、-r再帰的にファイルを検索する)だけ
  • 速度はあまり重視しておらず、工夫なし
  • 常にカラー表示。「リダイレクトしていたらエスケープシーケンスを抑制」とか別にしない
  • 色使いは jvgrepパクリ 互換

貴殿も、オレオレ grep を作ってみませんか?

Lua 5.1 向け shebang for Windows

@lua5.1.exe -e "_,_,b=io.input([[%~f0]]):read('*l','*l','*a');assert(loadstring('\n'..b,[[%~f0]]))()"
@exit /b %ERRORLEVEL%

print('ahaha')
x

5行目にわざと Lua としては不適切な文字 x を入れている。これを実行すると

lua: (command line):1: [string "C:\Users\USERNAME\foo.cmd"]:5: syntax error near <
eof>
stack traceback:
        [C]: in function 'assert'
        (command line):1: in main chunk
        [C]: in ?
exit status 1

5行目がおかしいというエラーがちゃんと出る。x を消すと

$ foo
ahaha

とちゃんと実行される。