Kakao uygulamasından bir terminal komutu yürütme


204

grepObjective-C Cocoa uygulamamdan nasıl bir terminal komutu (gibi ) yürütebilirim ?


2
Sadece açık
olanı belirtiyorum

@ Daij-Djan bu hiç doğru değil, en azından macOS'ta değil. Bir korumalı alan MacOS uygulaması gibi yerlerde ikililerinizin çalıştırabilirsiniz /usr/binnerede grephayatlarında.
jeff-h

Hayır. Lütfen bana yanlış olduğunu kanıtlayın;) ist nstask'ta sanal alanınızda olmayan bir şeyi çalıştırmayacaksınız.
Daij-Djan

Yanıtlar:


282

Kullanabilirsiniz NSTask. İşte çalışacak bir örnek ' /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 ve NSFileHandle görevin standart çıktısını yönlendirmek için kullanılır.

Objective-C uygulamanızdan işletim sistemi ile etkileşim hakkında daha ayrıntılı bilgi için, Apple'ın Geliştirme Merkezi: İşletim Sistemi ile Etkileşim hakkında bu belgeyi görebilirsiniz. .

Düzenleme: NSLog sorunu için dahil düzeltme

Bash aracılığıyla bir komut satırı yardımcı programını çalıştırmak için NSTask kullanıyorsanız, NSLog'un çalışmasını sağlamak için bu sihirli satırı eklemeniz gerekir:

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

Bir açıklama burada: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask


1
Evet, 'arguments = [NSArray dizisiWithObjects: @ "- e", @ "foo", @ "bar.txt", nil];'
Gordon Wilson

14
Cevabınızda küçük bir aksaklık var. NSPipe, okunduğunda temizlenen bir ara belleğe (işletim sistemi düzeyinde ayarlanır) sahiptir. Arabellek dolarsa, NSTask kilitlenir ve uygulamanız da süresiz olarak kilitlenir. Hata mesajı görünmez. NSTask çok fazla bilgi döndürürse bu olabilir. Çözüm kullanmaktır NSMutableData *data = [NSMutableData dataWithCapacity:512];. Sonra while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }. Ve [data appendData:[file readDataToEndOfFile]];while-loop çıktıktan sonra bir tane daha olması gerektiğine "inanıyorum" .
Dave Gallagher

2
Siz bunu yapmazsanız hatalar ortaya çıkmaz (bunlar yalnızca günlükte yazdırılır): [task setStandardError: pipe];
Mike Sprague

1
Bu ARC ve Obj-C dizi değişmez değerleri ile güncellenebilir. Örneğin pastebin.com/sRvs3CqD
bames53

1
Hataları düzeltmek de iyi bir fikirdir. task.standardError = pipe;
vqdave

43

kent'in makalesi bana yeni bir fikir verdi. bu runCommand yönteminin bir komut dosyasına ihtiyacı yoktur, yalnızca bir komut satırı tarafından çalıştırılır:

- (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;
}

Bu yöntemi şu şekilde kullanabilirsiniz:

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

1
Bu, çoğu durumu iyi işler, ancak bir döngüde çalıştırırsanız, çok fazla açık dosya tanıtıcısı nedeniyle bir istisna oluşturur. Ekleyerek düzeltilebilir: [file closeFile]; sonra DataToEndOfFile.
David Stein

@DavidStein: Bence runCommand yöntemi sarmak için autoreleasepool kullanmak yerine gibi görünüyor. Aslında, yukarıdaki kod ARC olmayanları da dikkate almaz.
16:39

@Kenial: Oh, bu çok daha iyi bir çözüm. Ayrıca, kapsamdan ayrıldıktan sonra kaynakları derhal serbest bırakır.
David Stein

/ bin / ps: Operasyona izin verilmiyor, başarılı olamıyorum, neden?
Naman Vaishnav

40

paylaşma ruhu içinde ... Bu, kabuk komut dosyalarını çalıştırmak için sık kullandığım bir yöntem. ürün paketinize (derlemenin kopya aşamasında) bir komut dosyası ekleyebilir ve komut dosyasının çalışma zamanında okunmasını ve çalışmasını sağlayabilirsiniz. not: bu kod script'i privateFrameworks alt yolunda arar. Uyarı: Bu, dağıtılan ürünler için bir güvenlik riski olabilir, ancak şirket içi geliştirmemiz için, uygulamayı yeniden derlemeden basit şeyleri (rsync'in hangi ... gibi) özelleştirmenin kolay bir yoludur, ancak kabuk betiği.

//------------------------------------------------------
-(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);    
}
//------------------------------------------------------

Düzenleme: NSLog sorunu için dahil düzeltme

Bash aracılığıyla bir komut satırı yardımcı programını çalıştırmak için NSTask kullanıyorsanız, NSLog'un çalışmasını sağlamak için bu sihirli satırı eklemeniz gerekir:

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

Bağlamda:

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

Bir açıklama burada: http://www.cocoadev.com/index.pl?NSTask


2
Açıklama bağlantısı öldü.
Jonny

Bu komutu çalıştırmak istiyorum "system_profiler SPApplicationsDataType -xml" ama bu hatayı alıyorum "başlatma yolu erişilemiyor"
Vikas Bansal

25

Swift'te nasıl yapacağınız aşağıda açıklanmıştır

Swift 3.0 için değişiklikler:

  • NSPipe yeniden adlandırıldı Pipe

  • NSTask yeniden adlandırıldı Process


Bu, yukarıdaki inkit'in Objective-C cevabına dayanmaktadır. O bir şekilde yazdım kategorisinde üzerinde NSString- Swift için, bir haline uzatma ait String.

uzatma String.runAsCommand () -> Dize

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 ---"
        }
    }
}

Kullanımı:

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

    ya da sadece:

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

Misal:

@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()

}

Not Processokunan sonuç Pipebir bir NSStringnesne. Bir hata dizesi olabilir ve boş bir dize olabilir, ancak her zaman bir olmalıdır NSString.

Yani, sıfır olmadığı sürece sonuç bir Swift olarak yayınlanabilir Stringve geri dönebilir.

Herhangi bir nedenle NSStringdosya verilerinden hiçbir değer başlatılamazsa, işlev bir hata mesajı döndürür. İşlev isteğe bağlı olarak döndürmek için yazılmış olabilir String?, ancak kullanımı garip olurdu ve yararlı bir amaca hizmet etmeyecektir, çünkü bunun gerçekleşmesi pek olası değildir.


1
Gerçekten Güzel ve Zarif bir şekilde! Bu cevabın daha fazla oyu olmalı.
XueYu

Çıktıya ihtiyacınız yoksa. @DiscardableResult argümanını runCommand yönteminin önüne veya üstüne ekleyin. Bu, yöntemi bir değişkene koymak zorunda kalmadan çağırmanıza izin verir.
Lloyd Keijzer

let sonuç = Dize (bayt: fileHandle.readDataToEndOfFile (), kodlama:
String.Encoding.utf8

18

Objective-C (Swift için aşağıya bakın)

Daha okunabilir, daha az yedekli olmak için üst yanıttaki kodu temizledik , tek satırlı yöntemin avantajlarını ekledik ve bir NSString kategorisine dönüştürdüm

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

Uygulama:

@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

Kullanımı:

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

Ve eğer çıkış kodlama ile ilgili sorunlar yaşıyorsanız:

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

Umarım gelecekteki bana olacak kadar faydalıdır. (Selam, sen!)


Hızlı 4

İşte bir Swift örneği verme kullanımı var Pipe, ProcessveString

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)
    }
}

Kullanımı:

let output = "echo hello".run()

2
Gerçekten, kodunuz benim için çok yararlı oldu! Swift olarak değiştirdim ve aşağıda başka bir cevap olarak gönderdim.
ElmerCat

14

Gerçekten Objective-C'ye özgü bir yol arıyorsanız , fork , exec ve wait çalışmalıdır. forkçalışmakta olan programın bir kopyasını oluşturur, çalışmakta olan programı execyenisiyle değiştirir waitve alt işlemin çıkmasını bekler. Örneğin (hata kontrolü olmadan):

#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. */

Komutu kabuğun komut satırından yazmış gibi çalıştıran bir sistem de vardır . Daha basit, ancak durum üzerinde daha az kontrole sahipsiniz.

Bir Mac uygulaması üzerinde çalıştığınızı varsayıyorum, bu yüzden bağlantılar bu işlevler için Apple'ın belgelerine aittir, ancak hepsi bu POSIXyüzden bunları herhangi bir POSIX uyumlu sistemde kullanmalısınız.


Bu çok eski bir cevap olduğunu biliyorum ama bunu söylemeliyim: bu dışlama işlemek için trheads kullanmak için mükemmel bir yoldur. tek dezavantajı, tüm programın bir kopyasını oluşturmasıdır. yani bir kakao uygulaması için ben güzel bir yaklaşım için @ GordonWilson ile gitmek istiyorum, ve ben bir komut satırı uygulaması üzerinde çalışıyorsanız bu bunu yapmanın en iyi yoludur. thanks (üzgünüm kötü ingilizcem)
Nicos Karalis

11

Ayrıca iyi eski POSIX sistemi ("echo -en '\ 007'");


6
BU KOMUTU ÇALIŞTIRMAYIN. (Bu komutun ne yaptığını bilmiyorsanız)
justin

4
Biraz daha güvenli bir şeye değiştirdi… (bip sesi)
nes1983 23

Bu konsolda bir hata atmayacak mı? Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
cwd

1
Hmm. Belki ters eğik çizgiden iki kez kaçmak zorundasınız.
nes1983

/ usr / bin / echo ya da başka bir şey çalıştırın. rm -rf sert ve konsoldaki unicode hala berbat :)
Maxim Veksler

8

Bu "C" fonksiyonunu yazdım, çünkü NSTaskiğrenç ..

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, tam / açık olmak adına…

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

Yıllar sonra, Chala benim için şaşırtıcı bir karmaşa var ... ve yukarıdaki brüt eksikliklerimi düzeltme yeteneğime az bir inançla - sunduğum tek zeytin dalı, @ inket'in kemikler için en cevabı olan cevabının rezhuzhed versiyonu, dostum için puristler / ayrıntılar ...

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]; });
}

1
outP herhangi bir hatada tanımlanmamışsa, chareof_read, sizeof (ssize_t)! = sizeof (int) 'in olduğu herhangi bir mimarideki fread () dönüş değeri için çok küçük, ya BUFSIZ baytından daha fazla çıktı istersek ne olur? Çıktı UTF-8 değilse ne olur? Pclose () bir hata döndürürse ne olur? Fread () hatasını nasıl raporlarız?
-oder

@ ObjectiveC-oder D'oh - Bilmem. Lütfen, söyle bana (olduğu gibi .. düzenle)!
Alex Gray

3

Custos Mortem şunları söyledi:

Kimsenin gerçekten engelleme / engelleme yapma sorunlarına girmediğine şaşırdım

NSTaskAşağıdakileri okumakla ilgili engelleme / engellemeyen çağrı sorunları için :

asynctask.m - NSTask ile veri işlemek için eşzamansız stdin, stdout ve stderr akışlarının nasıl uygulanacağını gösteren örnek kod

Asynctask.m kaynak kodu GitHub'da bulunabilir .


Benim Bkz katkı engellenmeyen versiyonu için
Guruniverse

3

Yukarıdaki birkaç mükemmel yanıta ek olarak, arka planda komut çıktısını işlemek ve engelleme mekanizmasını önlemek için aşağıdaki kodu kullanın [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];
}

Benim için tüm farkı yaratan çizgi [self performSelectorInBackground: @selector (collectTaskOutput :) withObject: file];
neowinston

2

Veya Objective C sadece üstte bazı OO katmanı olan C olduğundan, posix karşılaştırma parçalarını kullanabilirsiniz:

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[]); 

Bunlar unistd.h başlık dosyasından eklenir.


2

Terminal komutu Yönetici Ayrıcalığı (aka sudo) gerektiriyorsa , AuthorizationExecuteWithPrivilegesbunun yerine kullanın. Aşağıdaki "com.stackoverflow.test" adlı bir dosya oluşturur "/ System / Library / Caches" kök dizinidir.

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); 

4
Bu, OS X 10.7'den beri resmen kullanımdan kaldırıldı
Sam Washburn

2
.. ama ne olursa olsun bunu yapmaya devam ediyor, çünkü bunu yapmanın tek yolu ve birçok montajcı buna güveniyor.
Thomas Tempelmann
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.