IT story

Cocoa 앱에서 터미널 명령 실행

hot-time 2020. 5. 8. 08:23
반응형

Cocoa 앱에서 터미널 명령 실행


grepObjective-C Cocoa 애플리케이션에서 터미널 명령 (예 :)을 어떻게 실행할 수 있습니까?


사용할 수 있습니다 NSTask. 다음은 ' /usr/bin/grep foo bar.txt'를 실행하는 예입니다 .

int pid = [[NSProcessInfo processInfo] processIdentifier];
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *file = pipe.fileHandleForReading;

NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/grep";
task.arguments = @[@"foo", @"bar.txt"];
task.standardOutput = pipe;

[task launch];

NSData *data = [file readDataToEndOfFile];
[file closeFile];

NSString *grepOutput = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (@"grep returned:\n%@", grepOutput);

NSPipe그리고 NSFileHandle작업의 표준 출력을 리디렉션하는 데 사용됩니다.

Objective-C 응용 프로그램 내에서 운영 체제와의 상호 작용에 대한 자세한 내용은 Apple 개발 센터 : 운영 체제와 상호 작용 에서이 문서를 볼 수 있습니다 .

편집 : NSLog 문제에 대한 수정 포함

NSTask를 사용하여 bash를 통해 명령 행 유틸리티를 실행하는 경우 NSLog가 계속 작동하도록하려면이 매직 라인을 포함시켜야합니다.

//The magic line that keeps your log where it belongs
task.standardOutput = pipe;

설명은 여기에 있습니다 : https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask


공유의 정신에서 ... 이것은 쉘 스크립트를 실행하기 위해 자주 사용하는 방법입니다. 빌드의 복사 단계에서 제품 번들에 스크립트를 추가 한 후 런타임에 스크립트를 읽고 실행할 수 있습니다. 참고 :이 코드는 privateFrameworks 하위 경로에서 스크립트를 찾습니다. 경고 : 이는 배포 된 제품에 대한 보안 위험이 될 수 있지만 자체 개발의 경우 응용 프로그램을 다시 컴파일하지 않고 간단한 작업 (예 : rsync로 호스트 할 호스트 등)을 사용자 지정하는 쉬운 방법입니다. 번들의 쉘 스크립트.

//------------------------------------------------------
-(void) runScript:(NSString*)scriptName
{
    NSTask *task;
    task = [[NSTask alloc] init];
    [task setLaunchPath: @"/bin/sh"];

    NSArray *arguments;
    NSString* newpath = [NSString stringWithFormat:@"%@/%@",[[NSBundle mainBundle] privateFrameworksPath], scriptName];
    NSLog(@"shell script path: %@",newpath);
    arguments = [NSArray arrayWithObjects:newpath, nil];
    [task setArguments: arguments];

    NSPipe *pipe;
    pipe = [NSPipe pipe];
    [task setStandardOutput: pipe];

    NSFileHandle *file;
    file = [pipe fileHandleForReading];

    [task launch];

    NSData *data;
    data = [file readDataToEndOfFile];

    NSString *string;
    string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
    NSLog (@"script returned:\n%@", string);    
}
//------------------------------------------------------

편집 : NSLog 문제에 대한 수정 포함

NSTask를 사용하여 bash를 통해 명령 행 유틸리티를 실행하는 경우 NSLog가 계속 작동하도록하려면이 매직 라인을 포함시켜야합니다.

//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];

문맥:

NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];

설명은 여기에 있습니다 : http://www.cocoadev.com/index.pl?NSTask


켄트의 기사는 새로운 아이디어를 주었다. 이 runCommand 메소드는 스크립트 파일이 필요하지 않으며 명령 행을 한 줄씩 실행합니다.

- (NSString *)runCommand:(NSString *)commandToRun
{
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/bin/sh"];

    NSArray *arguments = [NSArray arrayWithObjects:
                          @"-c" ,
                          [NSString stringWithFormat:@"%@", commandToRun],
                          nil];
    NSLog(@"run command:%@", commandToRun);
    [task setArguments:arguments];

    NSPipe *pipe = [NSPipe pipe];
    [task setStandardOutput:pipe];

    NSFileHandle *file = [pipe fileHandleForReading];

    [task launch];

    NSData *data = [file readDataToEndOfFile];

    NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return output;
}

이 방법을 다음과 같이 사용할 수 있습니다.

NSString *output = runCommand(@"ps -A | grep mysql");

Swift에서 수행하는 방법은 다음과 같습니다.

스위프트 3.0의 변경 사항 :

  • NSPipe 이름이 변경되었습니다 Pipe

  • NSTask 이름이 변경되었습니다 Process


이것은 위의 inkit의 Objective-C 답변을 기반으로합니다. 그는로 쓴 카테고리NSString- 스위프트를 들어,이된다 확장String.

확장 String.runAsCommand ()-> 문자열

extension String {
    func runAsCommand() -> String {
        let pipe = Pipe()
        let task = Process()
        task.launchPath = "/bin/sh"
        task.arguments = ["-c", String(format:"%@", self)]
        task.standardOutput = pipe
        let file = pipe.fileHandleForReading
        task.launch()
        if let result = NSString(data: file.readDataToEndOfFile(), encoding: String.Encoding.utf8.rawValue) {
            return result as String
        }
        else {
            return "--- Error running command - Unable to initialize string from file data ---"
        }
    }
}

용법:

let input = "echo hello"
let output = input.runAsCommand()
print(output)                        // prints "hello"

    또는 그냥 :

print("echo hello".runAsCommand())   // prints "hello" 

예:

@IBAction func toggleFinderShowAllFiles(_ sender: AnyObject) {

    var newSetting = ""
    let readDefaultsCommand = "defaults read com.apple.finder AppleShowAllFiles"

    let oldSetting = readDefaultsCommand.runAsCommand()

    // Note: the Command results are terminated with a newline character

    if (oldSetting == "0\n") { newSetting = "1" }
    else { newSetting = "0" }

    let writeDefaultsCommand = "defaults write com.apple.finder AppleShowAllFiles \(newSetting) ; killall Finder"

    _ = writeDefaultsCommand.runAsCommand()

}

메모 Process(가)에서 읽을 결과를 Pipe입니다 NSString객체입니다. 오류 문자열 일 수도 있고 빈 문자열 일 수도 있지만 항상이어야합니다 NSString.

따라서 0이 아닌 한 결과는 스위프트로 캐스팅되어 String반환 될 수 있습니다 .

어떤 이유로 NSString파일 데이터에서 전혀 초기화 할 수 없는 경우 , 함수는 오류 메시지를 리턴합니다. 이 함수는 optional을 반환하도록 작성되었을 수 String?있지만 사용하기가 어려우며 이것이 발생할 가능성이 적기 때문에 유용한 목적으로 사용되지 않습니다.


Objective-C (Swift는 아래 참조)

가독성을 높이고 덜 가독성을 높이기 위해 최상위 답변의 코드를 정리 하고 한 줄 방법 의 이점을 추가하고 NSString 범주로 만들었습니다.

@interface NSString (ShellExecution)
- (NSString*)runAsCommand;
@end

이행:

@implementation NSString (ShellExecution)

- (NSString*)runAsCommand {
    NSPipe* pipe = [NSPipe pipe];

    NSTask* task = [[NSTask alloc] init];
    [task setLaunchPath: @"/bin/sh"];
    [task setArguments:@[@"-c", [NSString stringWithFormat:@"%@", self]]];
    [task setStandardOutput:pipe];

    NSFileHandle* file = [pipe fileHandleForReading];
    [task launch];

    return [[NSString alloc] initWithData:[file readDataToEndOfFile] encoding:NSUTF8StringEncoding];
}

@end

용법:

NSString* output = [@"echo hello" runAsCommand];

그리고 경우에 당신은 출력 인코딩에 문제가있어 :

// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];

그것이 미래에 나에게 도움이 되길 바랍니다. (안녕하세요!)


스위프트 4

다음의 스위프트 예를 만드는 사용이다 Pipe, ProcessString

extension String {
    func run() -> String? {
        let pipe = Pipe()
        let process = Process()
        process.launchPath = "/bin/sh"
        process.arguments = ["-c", self]
        process.standardOutput = pipe

        let fileHandle = pipe.fileHandleForReading
        process.launch()

        return String(data: fileHandle.readDataToEndOfFile(), encoding: .utf8)
    }
}

용법:

let output = "echo hello".run()

Objective-C 고유의 방법을 찾지 않는다면 fork , execwait 가 작동해야합니다. fork현재 실행중인 프로그램의 사본을 작성 exec하고 현재 실행중인 프로그램을 새 프로그램으로 바꾸고 wait서브 프로세스가 종료 될 때까지 기다립니다. 예를 들어 (오류 검사없이) :

#include <stdlib.h>
#include <unistd.h>


pid_t p = fork();
if (p == 0) {
    /* fork returns 0 in the child process. */
    execl("/other/program/to/run", "/other/program/to/run", "foo", NULL);
} else {
    /* fork returns the child's PID in the parent. */
    int status;
    wait(&status);
    /* The child has exited, and status contains the way it exited. */
}

/* The child has run and exited by the time execution gets to here. */

쉘의 명령 행에서 입력 한 것처럼 명령을 실행하는 system있습니다 . 더 간단하지만 상황에 대한 통제력이 떨어집니다.

I'm assuming you're working on a Mac application, so the links are to Apple's documentation for these functions, but they're all POSIX, so you should be to use them on any POSIX-compliant system.


There is also good old POSIX system("echo -en '\007'");


I wrote this "C" function, because NSTask is obnoxious..

NSString * runCommand(NSString* c) {

    NSString* outP; FILE *read_fp;  char buffer[BUFSIZ + 1];
    int chars_read; memset(buffer, '\0', sizeof(buffer));
    read_fp = popen(c.UTF8String, "r");
    if (read_fp != NULL) {
        chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
        if (chars_read > 0) outP = $UTF8(buffer);
        pclose(read_fp);
    }   
    return outP;
}

NSLog(@"%@", runCommand(@"ls -la /")); 

total 16751
drwxrwxr-x+ 60 root        wheel     2108 May 24 15:19 .
drwxrwxr-x+ 60 root        wheel     2108 May 24 15:19 ..

oh, and for the sake of being complete / unambiguous…

#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])

Years later, C is still a bewildering mess, to me.. and with little faith in my ability to correct my gross shortcomings above - the only olive branch I offer is a rezhuzhed version of @inket's answer that is barest of bones, for my fellow purists / verbosity-haters...

id _system(id cmd) { 
   return !cmd ? nil : ({ NSPipe* pipe; NSTask * task;
  [task = NSTask.new setValuesForKeysWithDictionary: 
    @{ @"launchPath" : @"/bin/sh", 
        @"arguments" : @[@"-c", cmd],
   @"standardOutput" : pipe = NSPipe.pipe}]; [task launch];
  [NSString.alloc initWithData:
     pipe.fileHandleForReading.readDataToEndOfFile
                      encoding:NSUTF8StringEncoding]; });
}

Custos Mortem said:

I'm surprised no one really got into blocking/non-blocking call issues

For blocking/non-blocking call issues regarding NSTask read below:

asynctask.m -- sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask

Source code of asynctask.m is available at GitHub.


Or since Objective C is just C with some OO layer on top you can use the posix conterparts:

int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0);
int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0);
int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]); 

They are included from unistd.h header file.


If the Terminal command requires Administrator Privilege (aka sudo), use AuthorizationExecuteWithPrivileges instead. The following will create a file named "com.stackoverflow.test" is the root directory "/System/Library/Caches".

AuthorizationRef authorizationRef;
FILE *pipe = NULL;
OSStatus err = AuthorizationCreate(nil,
                                   kAuthorizationEmptyEnvironment,
                                   kAuthorizationFlagDefaults,
                                   &authorizationRef);

char *command= "/usr/bin/touch";
char *args[] = {"/System/Library/Caches/com.stackoverflow.test", nil};

err = AuthorizationExecuteWithPrivileges(authorizationRef,
                                         command,
                                         kAuthorizationFlagDefaults,
                                         args,
                                         &pipe); 

In addition to the several excellent answers above, I use the following code to process the output of the command in the background and avoid the blocking mechanism of [file readDataToEndOfFile].

- (void)runCommand:(NSString *)commandToRun
{
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/bin/sh"];

    NSArray *arguments = [NSArray arrayWithObjects:
                          @"-c" ,
                          [NSString stringWithFormat:@"%@", commandToRun],
                          nil];
    NSLog(@"run command:%@", commandToRun);
    [task setArguments:arguments];

    NSPipe *pipe = [NSPipe pipe];
    [task setStandardOutput:pipe];

    NSFileHandle *file = [pipe fileHandleForReading];

    [task launch];

    [self performSelectorInBackground:@selector(collectTaskOutput:) withObject:file];
}

- (void)collectTaskOutput:(NSFileHandle *)file
{
    NSData      *data;
    do
    {
        data = [file availableData];
        NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] );

    } while ([data length] > 0); // [file availableData] Returns empty data when the pipe was closed

    // Task has stopped
    [file closeFile];
}

참고URL : https://stackoverflow.com/questions/412562/execute-a-terminal-command-from-a-cocoa-app

반응형