2022年5月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
無料ブログはココログ

« 2008年1月 | トップページ | 2008年3月 »

2008年2月28日 (木)

バッチ引数、FOR引数の有無を判定する。

よくあるのは、

IF "%1"=="" ECHO 引数なし

または、

IF "%~1"=="" ECHO 引数なし

ですが、 どちらも上手く行かないケースがあるので、

SET TEST=%1
IF NOT DEFINED TEST ECHO 引数なし

がお勧めです。

環境変数の部分文字列の有無(空)の判定も同様です。

IF "%AAA:~3%"=="" ECHO 空

のような判定は危険です。

SETLOCAL ENABLEDELAYEDEXPANSION
IF "!AAA:~3!"=="" ECHO 空

または、

SETLOCAL ENABLEDELAYEDEXPANSION
SET TEST=!AAA:~3!
IF NOT DEFINED TEST ECHO 空

なら安全です。

2008年2月22日 (金)

バッチファイルでテキストファイルを編集する。

テキストファイルを読み込んで、XXXXをYYYYに変更して、元のファイルに書き込む例です。

editfile.cmd ファイル

@echo off
setlocal disabledelayedexpansion
set TEST=%1
if not defined TEST goto :eof
set LINE=
for /f "delims=" %%0 in ('find /n /v "" ^< %1 ') do (
if not defined LINE call > %1
set LINE=%%0
setlocal enabledelayedexpansion
set LINE=!LINE:XXXX=YYYY!
>> %1 echo,!LINE:*]=!
endlocal
)

元のファイルに書き戻す関係で、一度、findコマンドで全行を読み出してから、1行目でファイルを空にして、各行を追加書きします。

2008年2月20日 (水)

ファイル名に=;,や全角空白を使うときは、半角空白も併せて使うべし。

一般的なドロップハンドラは、ファイル名に半角空白があるときだけ、"~"で囲むようです。
なので、ファイル名に=;,や全角空白だけが含まれているときは、"~"で囲まれません。

ところが、CMD.EXEのバッチやFORの引数では、半角空白、タブ、改行だけでなく、=;,も分離符です。:-(
と、ここまではよく知られていますが、さらに、全角空白も分離符みたいです。:-<

なので、ファイル名に=;,や全角空白を使うのは、出来れば、避けたほうがよいでしょう。
半角=;,の代わりに全角の=;、を使うとか、全角空白の代わりに半角空白1~2個を使うとか。

もし、それが避けがたいなら、敢えて、半角空白も併せて使うようにすればよいでしょう。

()&%!'`~^@なども、CMD.EXEで使われる制御文字なので、同様です。
中でも、%!は、"~"ではエスケープできないので、特に、避けたい。

例えば、エクスプローラのリネームで連番を付けると、AAA (n).XXXのような名前になりますが、これも、半角空白を入れているところが憎いところです。:-p

ほかに、#は、URLでエスケープに使われるので、避けたほうがよいでしょう。

同様に、[]!'は、Excelで使われるので、避けたほうがよいでしょう。

2008年2月19日 (火)

FOR /F の eol について

eol は end of line ではありません。除外行行頭文字です。exclusive of line ?

ヘルプの
eol=c           - 行末のコメント文字を指定します (1 文字)。
は嘘です。行頭が正しい?

eol文字のデフォルトは ; です。これは要注意です。eol=は必須と思ったほうが安全です。

eol文字がdelims文字に含まれると、eol文字は無効になります。

なので、eol文字をなくすには、delims文字と同じものを指定するとよいでしょう。

しかし、delims文字がないときに、eol文字をなくすのは、難しいです。

そこで、データに含まれない文字、制御文字を指定するとよいでしょう。
メモ帳では、DEL文字が使えます。入力は、Ctrl+BSキー、または、Alt+テンキーの0127です。
コマンドプロンプトでは、いろんなものが使えますが、^L や ^[ が使えます。

逆に、delims文字と同じものをeol文字に指定したいときは、困ります。

そういうときには、FOR文を2段組みにするとよいでしょう。

FOR /F "delims= eol= " %%I IN (...) DO FOR /F "eol= delims= " %%J IN ("%%I") DO ...
    delims=なし eol=空白                   eol=なし delims=空白

例えば、DIR /X の結果から短いファイル名を取り出すには、

FOR /F "delims= eol= " %%0 IN ('dir/x %1') DO FOR /F "tokens=4" %%4 IN ("%%0") DO ECHO %~dps1%%4
    delims=なし eol=空白                       eol=; delims=空白タブ

2008年2月18日 (月)

テンポラリファイルの代わりに環境変数を使う。

もし、テキストファイルの中身をひとまとめにして環境変数にセットし、各行にばらして取り出すことができれば、テンポラリファイルの代わりになります。

ポイントの第一は、
改行文字の扱いは難しいので、代わりにDEL文字を使います。
DEL文字はメモ帳でCtrl+BSキーかAlt+テンキーの0127で入ります。

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET Z=
FOR /F "delims=" %%I IN ('dir /b ^|find /n /v ""') DO SET Z=!Z!%%I

:LOOP
FOR /F "delims= tokens=1*" %%I IN ("!Z!") DO (
SET L=%%I
FOR /F "delims=[]" %%K IN ("%%I") DO ECHO=%%K !L:*]=!
SET Z=%%J
)
IF DEFINED Z GOTO :LOOP

他のポイントは、
遅延展開
空行認識と行番号付加のために、find /n /v ""
1行取り出しは、FOR /F "delims= tokens=1*"
行番号取り出しは、FOR /F "delims=[]"
行番号除去は、!L:*]=!
終端判定は、IF DEFINED Z

各ポイントには、それぞれ理由があります。

2008年2月17日 (日)

%~sIの短いファイル名が変ですね。(障害)

FOR変数やバッチパラメータの%~sIによる短いファイル名の取り出しには障害があるようです。

例えば、
for %I in (aa.aaaa) do echo %~sI
だと、
%~fsI C:\DOCUME~1\User\MYDOCU~1\AA4790~1.AAAaa
%~nsI AA4790~1
%~xsI .AAAaa

また、
for %I in (aaa.aaaa) do echo %~sI
だと、
%~fsI C:\DOCUME~1\User\MYDOCU~1\AAA~1.AAAa.aaaa
%~nsI AAA~1.AAAa
%~xsI .aaaa

などのように、後ろにゴミが付きます。ゴミの付き方は、いろいろです。

で、その代替方法ですが、

for %I in (aa.aaaa) do for %J in ("%~dpsI%~nxI") do echo %~sJ

とすればよいようです。

つまり、一度に短い名前にするのではなく、先にディレクトリパスを短い名前にしてから、次にファイル名を短い名前にするとよいようです。

2008年2月14日 (木)

エクスプローラで任意の順序で連番を付ける。

「詳細」ならカラムの昇順や降順で連番を付けることができますが、任意の順序に連番を付けるにはどうするか?
「アイコン」や「並べて表示」なら任意の順序に並べることができるので、そのあと複数選択、先頭を右クリックして「名前の変更」で連番を付ければよいでしょう。

先頭値は、[固定値 ](n).ext の形式で与えます。例えば、
AAA (1).txt
AAA (0).txt
AAA (10).txt
AAA (-10).txt
(1).txt
などです。

(n) AAA.txt のような名前にはできないので、一旦、(n).txt にしてから、
REN "*).txt" "*) AAA.txt"
で変えるとよいでしょう。

2008年2月13日 (水)

空のフォルダだけ、リカーシブにすべて削除するバッチファイル

RMDIRコマンドのオプションなしは、空のフォルダだけを削除します。
RMDIRコマンドの/Sオプションで、リカーシブにすべてのフォルダを削除することはできますが、空のフォルダだけを削除することはできません。
では、どうするか?

RmEmptyDir.cmd dir...

@ECHO OFF
FOR %%1 IN (%*) DO CALL :SUB %%1
GOTO :EOF
:SUB
FOR /D %%1 IN (%1\*) DO CALL :SUB "%%1"
RMDIR %1

2008年2月12日 (火)

元フォルダの中身をすべて先フォルダに移動するバッチファイル(その2)

MoveFolder.CMD 元フォルダ 先フォルダ

@if(0)==(0) ECHO OFF
CScript.exe //NoLogo //E:JScript "%~f0" "%~f1" "%~f2"
GOTO :EOF
@end
var fso=new ActiveXObject('Scripting.FileSystemObject');
if(fso.FolderExists(WScript.Arguments.Item(1))){
  var Folder=fso.GetFolder(WScript.Arguments.Item(0));
  for(var e=new Enumerator(Folder.Files);!e.atEnd();e.moveNext()) e.item().Move(WScript.Arguments.Item(1)+'\\');
  for(var e=new Enumerator(Folder.SubFolders);!e.atEnd();e.moveNext()) e.item().Move(WScript.Arguments.Item(1)+'\\');
}

2008年2月11日 (月)

元フォルダの中身をすべて別の先フォルダに移動するバッチファイル

ファイルだけなら、MOVE 元フォルダ\* 先フォルダ で行けます。
フォルダも、ひとつずつなら、MOVEコマンドで移動できます。
では、ファイルもフォルダもすべて移すにはどうするか?

MoveFolder.CMD 元フォルダ 先フォルダ

@IF '%2'=='' (
ECHO Usage: %~nx0 SourceDir DestDir
) ELSE (
FOR %%1 IN (%1\*) DO MOVE /-Y "%%1" %2\
FOR /D %%1 IN (%1\*) DO MOVE /-Y "%%1" %2\
)

2008年2月 8日 (金)

FileExists()でワイルドカードが使えない。その代替方法(その2)

性能はさておき、ワイルドカードを使って判定するには、Filter()が使えます。

Function FileExists(Spec)
Dim fso
Dim ParentFolderName
Dim Shell
Dim Folder
Dim FolderItems
Set fso=CreateObject("Scripting.FileSystemObject")
Spec=fso.GetAbsolutePathName(Spec)
ParentFolderName=fso.GetParentFolderName(Spec)
Set Shell=CreateObject("Shell.Application")
Set Folder=Shell.NameSpace(ParentFolderName)
Set FolderItems=Folder.Items()
FolderItems.Filter &H40,(fso.GetFileName(Spec))
FileExists=FolderItems.Count
End Function

ただし、Filter()はショートカットなどの非表示の拡張子を除外して判定します。

2008年2月 7日 (木)

FileExists()でワイルドカードが使えない。その代替方法

FileSystemObjectのCopyFile()、MoveFile()、DeleteFile()ではワイルドカードが使えるのに、FileExists()では使えません。:-(

以下のような代替方法は、ワイルドカード相当の判定式を作るのが面倒なのと、ファイル数の多いフォルダでは性能も遅そうです。:-(
For Each File In Folder.Files
  If ワイルドカード相当の判定式 Then Exit For
Next

そこで、ワイルドカードが使えるCopyFile()、MoveFile()で代替できないものか?

Function FileExists(Spec)
Dim fso
Dim ParentFolderName
Set fso=CreateObject("Scripting.FileSystemObject")
ParentFolderName=fso.GetParentFolderName(Spec)
If ParentFolderName="" Then ParentFolderName="."
On Error Resume Next
fso.CopyFile Spec,ParentFolderName
'fso.MoveFile Spec,ParentFolderName
FileExists=Err.Number<>53
End Function

CopyFile()、MoveFile()のいずれを使っても可能ですが、CopyFile()のほうが性能が良さそう。

2008年2月 6日 (水)

VBSファイルの実行を逐次化する。

多重に実行されると困る場合は、ロックを取って、逐次化します。

Option Explicit
Dim fso
Dim Lock

Set fso=CreateObject("Scripting.FileSystemObject")
Sub TryLock
  On Error Resume Next
  Set Lock=fso.OpenTextFile(WScript.ScriptFullName&":Lock",8,True)
End Sub
Do
  TryLock
  If Err.Number=0 Then Exit Do
  WScript.Sleep 1000
Loop
WScript.Echo "Locked"

ファイルの追加書きオープンで排他ロックを取ります。
ただし、スクリプトファイルだと、起動できなくなるので、NTFSストリームにします。
もし、NTFSでない場合は、:Lockを.Lockに変えて、別ファイルにするとよいでしょう。

2008年2月 4日 (月)

テキストファイルを使って連番を生成する。

テキストファイルなどにカウンタを書いて、+1で更新して連番を生成するのは、多重処理に耐えません。
では、どうするか?

テキストファイルへの空行の追加書きで、行数をカウンタに使えば、おk。

Option Explicit
Dim fso
Dim Lock
Dim SEQ

Set fso=CreateObject("Scripting.FileSystemObject")
Sub TryLock
  On Error Resume Next
  Set Lock=fso.OpenTextFile(WScript.ScriptFullName&".SEQ",8,True)
End Sub
Do
  TryLock
  If Err.Number=0 Then Exit Do
  WScript.Sleep 100
Loop
Lock.WriteBlankLines 1
SEQ=Lock.Line-1
Lock.Close
WScript.Echo SEQ

2008年2月 1日 (金)

FileSystemObjectでバイナリを読み書きする。

バイナリは、FileSystemObjectでなく、ADODB.Streamを使うのが普通ですが。。。

ANSI(シフトJIS)でも、それなりに読み書きできることがあります。
シフトJISの中のANSIコードの範囲で、コード=Asc(文字)、文字=Chr(コード)。

また、Unicodeなら、若干の限界がありますが、かなり読み書きできます。
2バイト単位の読み書き。先頭のバイトオーダマーク(0xFEFF)、末尾の端数バイト。
バイトオーダによって、0xHHhh がLittle EndianではhhHH、Big EndianではHHhhの順。
HH=AscW(文字) \ 256
hh=AscW(文字) Mod 256

また、スクリプトでは、ADODB.Streamならバイナリおk、とは行きません。
スクリプトには、バイト型やバイト配列がありません。:-(
読むときは、バイト配列をバイト文字列として、MidB()などの~B()を使います。
書くときは、Type=adTypeText、Charset="unicode"で、アンパック形式(0x00hh)のUnicode文字=ChrW(&Hhh)を書き込んで、Type=adTypeBinaryに変えて、Stream内でパックします。
ひとつのStreamでRead/Writeで詰めることもできますが、ふたつのStreamでCopyToしたほうが簡単です。

« 2008年1月 | トップページ | 2008年3月 »