概要
今回は例として、POSIXコマンドのchflags
を使って下記のフォルダ内のファイルを隠しファイルにすることを考える。
chflagsのパスは/usr/bin/chflags
である。
コマンド | 説明 |
---|---|
chflags hidden <ファイル名> | 隠し属性を付与する |
chflags nohidden <ファイル名> | 隠し属性を取る |
ターミナルでの呼び出し方は以下の通り。(~
には自分の環境のパスを挿入)
chflags hidden ~/Downloads/instance/SampleFolder/sample.txt
実装
これと同じことObjective-Cのコードで、NSTaskを用いて実行する。
NSString *sampleFilePathWithTilde = @"~/Downloads/instance/SampleFolder/sample.txt";
NSString *sampleFilePath = [sampleFilePathWithTilde stringByExpandingTildeInPath];
NSURL *chflags = [NSURL fileURLWithPath:@"/usr/bin/chflags"];
[self executeSelectCommand:chflags args:@[@"hidden", sampleFilePath]];
/**
@brief POSIXコマンドのパスとオプションを格納した配列を渡して実行する
@param commandURL POSIXコマンドのパス args 引数
@return 正常終了でYES
*/
- (BOOL)executeSelectCommand:(NSURL *)commandURL args:(NSArray *)args {
// タスクオブジェクトを準備する
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:[commandURL path]];
[task setArguments:args];
// 読み込み元のパイプを作成する
NSPipe *outPipe = [[NSPipe alloc] init];
[task setStandardOutput:outPipe];
// プロセスを起動する
[task launch];
// タスクが正常に終了することを確認する
[task waitUntilExit];
int status = [task terminationStatus];
// ステータスを確認する
if (status != 0) {
return NO;
}
return YES;
}
実行後、隠し属性になっていることを確認。
上記のコードの改善
SampleFolder
内のファイルを全て隠し属性にしようと考える。
ターミナルでの呼び出し方は以下の通り。(~
には自分の環境のパスを挿入)
chflags hidden ~/Downloads/instance/SampleFolder/*
しかし同じことを上記のコードで行おうとすると、ワイルドカードである「*」を展開できないという不都合が生じる。
[self executeSelectCommand:chflags args:@[@"hidden", @"*"]];
chflags: *: No such file or directory
ディレクトリが見つからないというエラー。
「*」を展開できていないようである。
問題の詳細
- shellを通すと、*を展開できるとのこと。
NSTask doesn't expand the * in your path. But if you invoke the command through the shell (i.e. /bin/bash), it will:
- またPOSIXコマンドのフルパス指定も面倒。これもShellを通すことで解決できる。
-NSTaskで外部コマンドを実行するときには"/bin/sh -c"を経由させると幸せらしい
NSTaskで外部コマンドを実行する場合、外部コマンドをフルパスで指定しないといけないらしい。マシン環境によってコマンドはマシンによって"/opt/local/bin"だったり"/usr/local/bin"だったりするので、フルパスでコマンドを指定すると動かなかったりするので、"/bin/sh -c"を経由させてコマンドの実行パスを解決すると幸せになれるみたいです。
- 以上より、下記の通り手を加えた。
- よりターミナルの操作に近い形で呼び出せるように引数を変更。
- 第一引数は
NSArray
からNSString
にした。 - 第二引数はコマンド実行前に
cd
してカレントパスを変更のと同義。
- 第一引数は
- POSIXコマンドの呼び出しにShellを通すようにした。
- よりターミナルの操作に近い形で呼び出せるように引数を変更。
改善後の実装
NSString *sampleFolderPathWithTilde = @"~/Downloads/instance/SampleFolder";
NSString *sampleFolderPath = [sampleFolderPathWithTilde stringByExpandingTildeInPath];
NSURL *sampleFolderURL = [NSURL fileURLWithPath:sampleFolderPath];
[self executePosixThroughBashWithStatement:@"chflags hidden *" posixExecutingURL:sampleFolderURL];
/**
@brief Shellを通じてPosixコマンドを実行する(*を展開するために用意)
@param statement 引数の文字列 ex.@"chflags hidden *"
@param aCurrentDirectory Posixコマンドの実行上のパス(空文字可)
@return BOOL Posix実行正常終了でYES
*/
- (BOOL)executePosixThroughBashWithStatement:(NSString *)statement posixExecutingURL:(NSURL *)aCurrentDirectory {
NSArray *args = @[@"-c", statement];
// タスクオブジェクトを準備する
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/bin/sh"; // ワイルドカードを展開するためbashを経由する
task.arguments = args;
// 読み込み元のパイプを作成する
NSPipe *pOutput = [NSPipe pipe]; // 標準出力先
NSPipe *pError = [NSPipe pipe]; // エラー出力先
task.standardOutput = pOutput;
task.standardError = pError;
if (aCurrentDirectory.path.length != 0) {
task.currentDirectoryURL = aCurrentDirectory;
}
// プロセスを起動する
[task launch];
// タスクが正常に終了することを確認する
[task waitUntilExit];
NSData *dataError = [[pError fileHandleForReading] availableData]; //エラーのデータ
if (dataError.length != 0) {
NSLog(@"error-%s", [dataError bytes]); //エラー出力
}
NSData *dataOutput = [[pOutput fileHandleForReading] availableData]; //標準出力データ
NSString *stringOutput = [[NSString alloc] initWithData:dataOutput encoding:NSShiftJISStringEncoding]; //標準出力
if (stringOutput.length != 0) {
NSLog(@"\n*** UnixOutput ***\n%@", stringOutput);
}
// ステータスを確認する
if ([task terminationStatus] != 0) {
return NO;
}
return YES;
}
全て隠し属性になっていることを確認。
第一引数ですが、arguments
の要素を小分けにするとうまく行かなかったので、NSString
で書き直した。
NSArray *args = @[@"-c", @"chflags hidden *"];
下記のコードで実行したところエラーがでた。(最初の例ではうまく行っていたが何故だろうか)
NSArray *args = @[@"-c", @"chflags", @"hidden", @"*"];
error-usage: chflags [-fhv] [-R [-H | -L | -P]] flags file ...
G…
余談:ログへのアウトプットとかは返り値にしたりしたほうが、デバッグしやすいかもなとも思う。