Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Execute command in background

I use the following code which is execute command of "npm install" , now while debug I see that the command is taking about 10..15 sec to execute (depend on how much module I've). what I want is that this command will execute in background and the program will continue.

cmd := exec.Command(name ,args...)
cmd.Dir = entryPath

In debug I see that to move to next line tass about 10..15 sec...

I’ve two questions in this:

  1. How can I can do it? since I want to do something in parallel...
  2. How can I know when it finishes? To provide additional logic which is related to this command i.e. after the npm install is done I need to do other stuff.
like image 208
tj holwik Avatar asked Feb 01 '18 07:02

tj holwik


People also ask

How do I run a command in the background in Windows?

start /B command is the most given answer, but the command will be closed when the terminal closed. and then CTRL C, close the terminal, the command will continue to run in background. This is the most decent way I have found so far.

How do I run a command in the background and close a terminal?

In the new screen session, execute the command or script you wish to put in the background. Press Ctrl + A on your keyboard, and then D . This will detach the screen, then you can close the terminal, logout of your SSH session, etc, and the screen will persist.

How do I run a program in the background in bash?

If you want to push a command into the background, using & at the end is an easy way to do that. This way, you can issue a command in the background and continue to use your terminal as it runs. It comes with a catch, though. Using & doesn't disconnect the command away from you; it just pushes it into the background.


2 Answers

While in general you need goroutines to run something parallel (or more precisely concurrent), in case of running an external command or app in that manner does not require you to use goroutines (in fact, it's redundant).

This is because the exec.Cmd used to run commands has a Cmd.Start() method which starts the specified command but does not wait for it to complete. So you are free to do other stuff while it runs in the background, and when you need to wait for it to finish (and process its result), you may call Cmd.Wait() (which will block and wait for the command to complete).

This is how it could look like:

cmd := exec.Command("npm", "install", "other_params")
cmd.Dir = entryPath

if err := cmd.Start(); err != nil {
    log.Printf("Failed to start cmd: %v", err)
    return
}

// Do other stuff while cmd runs in background:
log.Println("Doing other stuff...")

// And when you need to wait for the command to finish:
if err := cmd.Wait(); err != nil {
    log.Printf("Cmd returned error: %v", err)
}

In contrast to Cmd.Start(), there is Cmd.Run() which starts the specified command and waits for it to complete, should you not need to run it in the "background". In fact Cmd.Run() is nothing more than a chaining of Cmd.Start() and Cmd.Wait() calls.

Note that when running in the "background", to get the output of the app, you can't call Cmd.Output() or Cmd.CombinedOutput() as they run then command and get its output (and you already started the command). If you need the output of the command, set a buffer to Cmd.Stdout which you can inspect / use after.

This is how it can be done:

cmd := exec.Command("npm", "install", "other_params")
cmd.Dir = entryPath
buf := &bytes.Buffer{}
cmd.Stdout = buf

if err := cmd.Start(); err != nil {
    log.Printf("Failed to start cmd: %v", err)
    return
}

// Do other stuff while cmd runs in background:
log.Println("Doing other stuff...")

// And when you need to wait for the command to finish:
if err := cmd.Wait(); err != nil {
    log.Printf("Cmd returned error: %v", err)
    // You may decide to continue or return here...
}

fmt.Println("[OUTPUT:]", buf.String())

If you also want to capture the standard error stream of the app, you may / have to do the same with Cmd.Stderr. Tip: you may set the same buffer to Cmd.Stdout and Cmd.Stderr, and then you'll get the combined output, this is guaranteed as per doc:

If Stdout and Stderr are the same writer, and have a type that can be compared with ==,
at most one goroutine at a time will call Write.

like image 112
icza Avatar answered Oct 08 '22 11:10

icza


To run a method in parallel, you can use goroutines and channels.

In goroutine, do your exec.Command operation. This will work in background.

And you can return information using channel from your goroutine.

And finally, you can check this value sent through channel. If you check immediately after calling goroutine, it will block until receives value from channel. So you need to check this after rest of the jobs that you want to do in parallel.

type Data struct {
    output []byte
    error  error
}

func runCommand(ch chan<- Data) {
    cmd := exec.Command("ls", "-la")
    data, err := cmd.CombinedOutput()
    ch <- Data{
        error:  err,
        output: data,
    }
}

func main() {
    c := make(chan Data)

    // This will work in background
    go runCommand(c)

    // Do other things here

    // When everything is done, you can check your background process result
    res := <-c
    if res.error != nil {
        fmt.Println("Failed to execute command: ", res.error)
    } else {
        // You will be here, runCommand has finish successfuly
        fmt.Println(string(res.output))
    }
}

See in action: Play Ground

Now, for OP's requirement how to implement this from another package.

From comment: Yes I understand this . but if I need to register from other package res := <-c how it should be done properly

You can try this: Play Ground. Check from another package check

Note: this link will not build

like image 35
Shahriar Avatar answered Oct 08 '22 11:10

Shahriar