[powershell]巨大なテキストファイルの最後の方だけ見るのに時間がかかる
PS> dir \\リモートのPC\巨大ファイル ディレクトリ: \\xxxx\xxx\bin\logs Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2022/01/12 20:25 68290811 巨大ファイル.log
サーバのログの監視などでしょっちゅう状態を知りたいときなどに、
Linuxのtailの代わりにこれをやると
date Get-Content \\リモートのPC\巨大ファイル -tail 20 date
2022年10月13日 木曜日 20:23:29 ~略~ 2022年10月13日 木曜日 20:26:03
2分半くらいかかった。もっと素早くぱぱっと見たい。
FileStreamのSeekを使ってから、StreamReaderに引き渡す。
byte数指定なので、仮に10000byte
using namespace System.IO using namespace System.Text date $infile="\\リモートのPC\巨大ファイル" $fs = [FileStream]::new($infile, [FileMode]::Open, [FileAccess]::Read, [FileShare]::ReadWrite) #Seek()を使いたい + FileShare指定したいのでこれで開く $fs.Seek(-10000, [SeekOrigin]::End) $st = [StreamReader]::new($fs, [Encoding]::GetEncoding("shift_jis")) #ReadToEnd(), ReadLine()などを使いたいので $st.ReadToEnd() $st.close() $fs.close() date
2022年10月13日 木曜日 20:22:00 ~略~ 2022年10月13日 木曜日 20:22:14
14秒。
[FileShare]::ReadWrite でサーバ側の更新処理を邪魔しないようにしている(はず)
powershell Excelにファイル一覧
立ち上げたExcelにカレントディレクトリのファイル名一覧を書き出す
($sheet = (New-Object -ComObject excel.application).workbooks.add().sheets.add()).Application.Visible=$true $r = $sheet.range("a1") dir -Name | foreach { $r.value = $_; $r=$r.Offset(1,0) }
最後の行をこれにすればサブディレクトリも含む全て
dir -Name -Recurse | foreach { $r.value = $_; $r=$r.Offset(1,0) }
powershell iniファイルをテスト用などでしょっちゅう書き換えるときのツール作ってみた
powershellの練習がてら作成。
コメントや空行などを維持したままで更新してくれる。
<# .SYNOPSIS iniファイルへの変更指示をコマンドラインから受け付けて処理する .example .\iniUtil セクション名1.パラメータ1=値1 セクション名2.パラメータ2=値2 スペース区切りなので=の両端にはスペースを入れない 値にダブルクオーテーションはあっても無くても同じ .outputs 現在のiniファイル名と同じで作成する 元ファイルは_bakタイムスタンプを付けてバックアップする #> using namespace System.Collections.Generic using namespace System.IO $inifile = [INIFile]::new(".\AAAA.ini") #iniファイル名が固定の場合。パラメータにしても良いだろう Write-Host "input file $($inifile.name)" $section = [Section]::new("") $lineno = 0 Get-Content $inifile.name | ForEach-Object { if($_ -match "^\[.+\]") { $inifile.sections.add($section.name, $section) $section = [Section]::new($_) Write-Host "input section $_" return #foreach-objectではcontinueではなくreturnで次ループへ移る } else { $line = [INILine]::new($_) } if($line.type -eq [LineType]::param){ if($section.lines.containskey($line.paramname)){ "警告 --------- 重複パラメータ 無視しました $($line.paramname)"} else{ $section.lines.add($line.paramname, $line) } } else { $section.lines.add($lineno, $line) } $lineno++ } $inifile.sections.add($section.name, $section) #最後は終わりの合図がないので強制 $changecount = 0 #変更箇所を反映 $args.ForEach({ $match = [regex]::Match($_, "^(.+)\.(.+)=(.+)$") #sec.key=val if(!$match.Success) { Write-Host " ---- invalid param $_" continue #foreachメソッドの場合は普通にこれで良い } $arg = [Arg]::new($match.Groups[1].value, $match.Groups[2].value, $match.Groups[3].value) $sec = $inifile.sections[$arg.sec] if($sec -eq $null){ $sec = [Section]::new("[$($arg.sec)]") Write-Host " ---- add Section $($arg.sec)" $inifile.sections.add($arg.sec, $sec) } $params = $sec.lines $arg.val = '"' + $arg.val + '"' if($params.containskey($arg.key)){ Write-Host " ---- change $($arg.sec).$($arg.key) = $($arg.val)" $params[$arg.key].value = $arg.val } else { Write-Host " ---- add $($arg.sec).$($arg.key) = $($arg.val)" $params.add($arg.key, [INILine]::new("$($arg.key) = $($arg.val)")) $params.add("", [INILine]::new("")) #空行も追加 } $changecount++ }) if($changecount -gt 0) { #changecountがあるときのみ書き換え $bakfile = $inifile.name + "_bak" + [DateTime]::now.ToString("yyyyMMddHHmmss") Copy-Item $inifile.name ($bakfile) #バックアップ Write-Host " ---- backup $bakfile" $inifile.save($inifile.name) Write-Host " ---- save $($inifile.name)" } enum LineType { param comment empty } class INILine { [LineType] $type [string] $paramname [string] $value INILine([String] $line){ #class内ではWrite-Outputは出力されない #Write-Host "new line constructor" if($line -match "^[ \t]*;") { $this.type = [LineType]::comment $this.value = $line } elseif($line -match "^[ \t]*$"){ $this.type = [LineType]::empty } else { $match = [regex]::Matches($line, "^[ \t]*(.+)=(.+)") if($match.success) { $this.type = [LineType]::param $this.paramname = $match.Groups[1].value.trim() $this.value = $match.Groups[2].value.trim() } else { Write-Host "ignore line $line" $this.type = [LineType]::empty } } } out([StreamWriter] $w){ switch ($this.type) { ([LineType]::comment) { $w.WriteLine("$($this.value)") } ([LineType]::empty) { $w.WriteLine("") } ([LineType]::param) { $w.WriteLine("$($this.paramname) = $($this.value)") } } } } class Section { [string] $name #nameが無ければ名前のないSection(ファイル先頭など) [Dictionary[[string],[INILine]]] $lines = [Dictionary[[string],[INILine]]]::new() Section([string] $s){ $this.name = [regex]::Match($s, "\[(.+)\]").groups[1].Value } out([StreamWriter] $w){ if($this.name -ne "") { $w.WriteLine("[$($this.name)]") } foreach($k in $this.lines.keys){ $this.lines[$k].out($w) } } } class INIFile { [string] $name; [Dictionary[[string],[Section]]] $sections = [Dictionary[[string],[Section]]]::new() INIFile ([string] $n){ $this.name = $n } save([string] $filename){ $outfile = [StreamWriter]::new((Get-Location).ToString() + "\" + $filename, $false, [System.Text.Encoding]::GetEncoding("shift-jis")) foreach($k in $this.sections.keys) { $this.sections[$k].out($outfile) } $outfile.Close() } } class Arg { [string] $sec [string] $key [string] $val Arg ($s, $k, $v){ $this.sec = $s $this.key = $k $this.val = $v } }
Powershell class, インスタンス, List 不思議な挙動
Aというクラスがあるとして
class A { [string]$s1 }
.Net のList型を用意
using namespace System.Collections.Generic $alist = New-Object List[A]
状態を見ると、List型にはAddというメソッドがあり、 A型を1つ入れられることが分かる。
$alist.GetType()
IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True List`1 System.Object
$alist.Add
OverloadDefinitions ------------------- void Add(A item) void ICollection[A].Add(A item) int IList.Add(System.Object value)
1つ入れてみる
$alist.Add( [A]::new())
型はもちろんそのまま
$alist.GetType()
IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True List`1 System.Object
個数は当然 1
$alist.Count
1
ここから本題。
powershellの配列には +=演算子(要素追加) がある。それを使ってみる。
$alist += New-Object A
$alist.GetType()
IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Object[] System.Array
形がObject[]に変わってしまった!しかし個数は正しいw
$alist.Count
2
List型にはその演算子はありません!って言ってほしい。
powershellでExcel操作+画像ファイルをクリップボード経由でシート貼り付け
ショートカット
cmd.exe /c powershell -noexit \app\scripts\toExcel2.ps1
を作り、そのショートカットへ複数フォルダをドラッグアンドドロップすると、
フォルダ毎別シートになって、ファイル名一覧をシートに書き込む。
拡張子が.DATまたは.csvのものは内容をシートに書き込む。
拡張子が.jpgのものは画像をシートに貼る。
#"\app\scripts\toExcel2.ps1"として保存 #ショートカット #cmd.exe /c powershell -noexit \app\scripts\toExcel2.ps1 #フォルダを複数ドラッグアンドドロップ using namespace System.Drawing; using namespace System.Windows.Forms Add-Type -AssemblyName System.Windows.Forms $excel = New-Object -ComObject Excel.Application $book = $excel.Workbooks.Add() $excel.Visible = "True" $excel.EnableEvents = "False" $excel.DisplayAlerts = "False" function execFolder($fol){ cd $fol $sh = $book.Sheets.Add() $sh.Name = (Get-Item .).BaseName echo $sh.Name $r = $sh.Cells(1,1) $files = dir -name $files.ForEach({ $r.Value = $_ $ext = (Get-Item $_).Extension echo $ext if($ext -eq ".DAT" -or $ext -eq ".csv") { $r.Offset(0,3).Value = cat $_ } $r = $r.Offset(1,0) }) echo $files $files = (dir *.jpg).Fullname $files.ForEach({ echo $r.Address() [Clipboard]::SetImage([Image]::FromFile($_)) [Application]::DoEvents() $sh.Paste($r) $r = $r.Offset(1,0) }) } $args.ForEach({ execFolder($_) }) $excel.EnableEvents = "True" $excel.DisplayAlerts = "True"
C++Builder UnicodeStringとchar*文字列の連結
これがあるとき
UnicodeString s = "xxx"
これはできるのに
UnicodeString where = " abc" + s + " = :" + s;
これは出来ない
UnicodeString where = "and " + " abc" + s + " = :" + s;
理由:
char*とUnicodeStringをつなぐときには+演算子と自動変換が include\windows\rtl\ustring.h に定義してあるが、
UnicodeString operator +(const char*, const UnicodeString&); UnicodeString operator +(const wchar_t*, const UnicodeString&); UnicodeString operator +(const char16_t*, const UnicodeString&); UnicodeString operator +(const char32_t*, const UnicodeString&); UnicodeString(const char* src); UnicodeString(const UnicodeString& src); UnicodeString(const WideChar* src, int len); UnicodeString(const char* src, int len); UnicodeString(const WideChar* src); #if defined(WIDECHAR_IS_WCHAR) UnicodeString(const char16_t* src, int numChar16 = -1); #endif UnicodeString(const char32_t* src, int numChar32 = -1); #if defined(WIDECHAR_IS_CHAR16) UnicodeString(const wchar_t* src, int numWChar = -1); #endif
char*が連続すると、char*とchar*では演算子が無いためコンパイルできない。
しかし、これはできる
UnicodeString where = "and " " abc" + s + " = :" + s;
理由:
"and " " abc" はプリプロセッサによってコンパイルよりも前に連結されるので + をつけなければ良い。
Sharepointのパスをエクスプローラで開く
このようにsites という言葉が2回でてくるパターンの場合、ブラウザのアドレスバーからURLをコピーし、ブラウザ内を右クリック
→ Edgeなら「開発者ツール」, Chromeだと「検証」
→「console」または「コンソール」タブでdecodeURIComponent関数を使い、
decodeURIComponent("https://xxxx.sharepoint.com/sites/xxxxxxxxxxxxxxxx/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2Fxxxxxxxxxxxxxxxxxxxx&viewid=xxxxxxxxxxxxxxxxx")
と実行すると、日本語を含んだパスがでてくるので、最後の"&viewid=xxxxxxxxxxxxxxxxx"の部分を取り除く
https://xxxx.sharepoint.com/sites/xxxxxxxxxxxxxxxx/Shared Documents/Forms/AllItems.aspx?id=/sites/xxxxxxxxxxxxxxxxxxxx
さらに、1つ目の"/sites/"と2つ目の"/sites/"の間の文字列を整理して
https://xxxx.sharepoint.com/sites/xxxxxxxxxxxxxxxxxxxx
このかたちにすれば、エクスプローラ(explorer.exe)で開くことができる。
さっきのdecodeURIComponent関数を使うところは
decodeURIComponent(location.href);
としても同じ結果になる。