0

I try to list files/folder in a director by using NSTask, how do I get the output in each line into a list? I try print it out and it only display the first line.

NSTask * list = [[NSTask alloc] init];
[list setLaunchPath:@"/bin/ls"];
[list setCurrentDirectoryPath:@"/"];

NSPipe * out = [NSPipe pipe];
[list setStandardOutput:out];

[list launch];
[list waitUntilExit];

NSFileHandle * read = [out fileHandleForReading];
NSData * dataRead = [read readDataToEndOfFile];
NSString * stringRead = [[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding]];
HBLogDebug(@"PATH: %@", stringRead); //Only the first line printed

NSMutableArray *outputlist = [[NSMutableArray alloc] init];

//This part here I'm not so sure how to proceed since it's only one line, I expected multiples line
for (NSString *s in stringRead?dataRead?){
    [outputlist addObject:s];
}

Thanks in advance!

Gregor Isack
  • 1,111
  • 12
  • 25
  • You `waitUntilExit` before getting the input with `fileHandleForReading`, maybe just drop the former so your process hasn't died before you try to get it's output? That said for this particular task you would be better of using one of the methods in the *Discovery Directory Contents* section of the [`NSFileManager` documentation](https://developer.apple.com/documentation/foundation/nsfilemanager). HTH – CRD Dec 13 '19 at 08:09
  • Remove the `waitUntilExit` will freeze the system/UI this code running from, unfortunately. Regarding `NSFileManager`, that will work, but for this purpose only, the code above just an example (`ls`) – Gregor Isack Dec 13 '19 at 08:26
  • As already mentioned you can use NSFileHandle's `readToEndOfFileInBackgroundAndNotify` to run the task in the background with a notification. From there just break up the result string at the line terminators (\n and/or \r), using NSString's `componentsSeparatedByString` or `componentsSeparatedByCharactersInSet` to get it into an array. – red_menace Dec 13 '19 at 16:20
  • @GregorIsack - removing `waitUntilExit` should not freeze your UI, if anything it could have the reverse effect – doing a wait *before* the synchronous read can cause a hang. I've added some working code as an answer which may help you figure out what is different with your code that causes the freeze. HTH – CRD Dec 14 '19 at 09:19

2 Answers2

1

After the comments here is a working example built in Xcode. The runCommand method is based on this answer updated and with optional use of waitUntilExit so you can see not using it does not freeze the app.

@implementation AppDelegate

// Arguments:
//    atPath: full pathname of executable
//    arguments: array of arguments to pass, or nil if none
// Return:
//    the standard output, or nil if any error
//
// This method blocks the caller until the called command completes
// (due to both readDataToEndOfFile & waitUntilExit)
// If this an issue either run on a thread or use the asynchronous read alternatives to readDataToEndOfFile
// If there is no need to check terminatation status the waitUntilExit and terminationStatus can be skipped
// by defining RUN_COMMAND_CHECK_TERMINATION as 0

#define RUN_COMMAND_CHECK_TERMINATION 0

+ (NSString *) runCommand:(NSString *)atPath withArguments:(nullable NSArray *)arguments
{
   NSTask *task = [NSTask new];
   NSPipe *pipe = [NSPipe new];

   task.standardOutput = pipe;     // pipe standard output

   task.launchPath = atPath;       // set path
   if(arguments)
      task.arguments = arguments;  // set arguments

   [task launch];                  // execute

   NSData *data = pipe.fileHandleForReading.readDataToEndOfFile; // read standard output

#if RUN_COMMAND_CHECK_TERMINATION
   [task waitUntilExit];           // verify process has exited to prevent terminationStatus faulting
                                   // must do this *after* read otherwise if pipe fills wait can block forever

   if (task.terminationStatus != 0) // check termination status & have output
      return nil;
#endif

   if (data == nil) // check termination status & have output
      return nil;

   return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; // return stdout as string
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
   NSString *stringRead =  [AppDelegate runCommand:@"/bin/ls" withArguments:@[@"/"]];
   NSLog(@"PATH: %@", stringRead);
   NSArray *outputList = [stringRead componentsSeparatedByString:@"\n"]; // Note: will produce an empty last line, removing that is left as an exercise
   NSLog(@"Lines: %@", outputList);
}

@end

If your code is freezing either its down to something strange with HBLogDebug (as the above uses NSLog) or something else in your code which you'll have to ferret out.

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
0

1.Use NSLog instead of HBLogDebug

`NSLog(@"PATH: %@", stringRead); //it prints all lines in my case`
  1. split the stringRead by newline characters

    for (NSString *s in [stringRead componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]){ [outputlist addObject:s]; }

  2. Better way to list files in objective-c for MACOS

    [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"<path>" error:nil]

Shakir Zareen
  • 232
  • 3
  • 15
  • Instead of `for (NSString *s in …) [outputlist addObject:s]` do `[outputlist addObjectsFromArray:…]`. – Willeke Dec 14 '19 at 13:52