1

The Bash command I used:

  1. $ ssh [email protected] ps -aux|grep -v \"grep\"|grep "/srv/adih/server/app.js"|awk '{print $2}'

    6373
    
  2. $ ssh [email protected] echo $(ps -aux|grep -v \"grep\"|grep "/srv/adih/server/app.js"|awk '{print $2}')

    8630
    

The first result is the correct one and the second one will change echo time I execute it. But I don't know why they are different.


What am I doing?

My workstation has very limited resources, so I use a remote machine to run my Node.js application. I run it using ssh [email protected] "cd /application && grunt serve" in debug mode. When I command Ctrl + C, the grunt task is stopped, but the application is is still running in debug mode. I just want to kill it, and I need to get the PID first.

2 Answers 2

5

The command substitution is executed by your local shell before ssh runs.

If your local system's name is here and the remote is there,

ssh there uname -n

will print there whereas

ssh there echo $(uname -n)  # should have proper quoting, too

will run uname -n locally and then send the expanded command line echo here to there to be executed.

As an additional aside, echo $(command) is a useless use of echo unless you specifically require the shell to perform wildcard expansion and whitespace tokenization on the output of command before printing it.

Also, grep x | awk { y } is a useless use of grep; it can and probably should be refactored to awk '/x/ { y }' -- but of course, here you are reinventing pidof so better just use that.

 ssh [email protected] pidof /srv/adih/server/app.js

If you want to capture the printed PID locally, the syntax for that is

pid=$(ssh [email protected] pidof /srv/adih/server/app.js)

Of course, if you only need to kill it, that's

ssh [email protected] pkill /srv/adih/server/app.js
Sign up to request clarification or add additional context in comments.

1 Comment

There are platform differences between pidof and pgrep and pkill implementations. You might need to pass in -f to examine the full argument list of the matched process.
2

Short answer: the $(ps ... ) command substitution is being run on the local computer, and then its output is sent (along with the echo command) to the remote computer. Essentially, it's running ssh [email protected] echo 8630.

Your first command is also probably not doing what you expect; the pipes are interpreted on the local computer, so it's running ssh [email protected] ps -aux, piping the output to grep on the local computer, piping that to another grep on the local computer, etc. I'm guessing that you wanted that whole thing to run on the remote computer so that the result could be used on the remote computer to kill a process.

Long answer: the order things are parsed and executed in shell is a bit confusing; with an ssh command in the mix, things get even more complicated. Basically, what happens is that the local shell parses the command line, including splitting it into separate commands (separated by pipes, ;, etc), and expanding $(command) and $variable substitutions (unless they're in single-quotes). It then removes the quotes and escapes (they've done their jobs) and passes the results as arguments to the various commands (such as ssh). ssh takes its arguments, sticks all the ones that look like parts of the remote command together with spaces between them, and sends them to a shell on the remote computer which does this process over again.

This means that quoting and/or escaping things like $ and | is necessary if you want them to be parsed/acted on by the remote shell rather than the local shell. And quotes don't nest, so putting quotes around the whole thing may not work the way you expect (e.g. if you're not careful, the $2 in that awk command might get expanded on the local computer, even though it looks like it's in single-quotes).

When things get messy like this, the easiest way is sometimes to pass the remote command as a here-document rather than as arguments to the ssh command. But you want quotes around the here-document delimiter to keep the various $ expansions from being done by the local shell. Something like this:

ssh [email protected] <<'EOF'
echo $(ps -aux|grep -v "grep"|grep "/srv/adih/server/app.js"|awk '{print $2}')
EOF

Note: be careful with indenting the remote command, since the text will be sent literally to the remote computer. If you indent it with tab characters, you can use <<- as the here-document delimiter (e.g. <<-'EOF') and it'll remove the leading tabs.

EDIT: As @tripleee pointed out, there's no need for the multiple greps, since awk can do the whole job itself. It's also unnecessary to exclude the search commands from the results (grep -v grep) because the "/" characters in the pattern need to be escaped, meaning that it won't match itself.. So you can simplify the pipeline to:

ps -aux | awk '/\/srv\/adih\/server\/app.js/ {print $2}'

Now, I've been assuming that the actual goal is to kill the relevant pid, and echo is just there for testing. If that's the case, the actual command winds up being:

ssh [email protected] <<'EOF'
kill $(ps -aux | awk '/\/srv\/adih\/server\/app.js/ {print $2}')
EOF

If that's not right, then the whole echo $( ) thing is best skipped entirely. There's no reason to capture the pipeline's output and then echo it, just run it and let it output directly.

And if pkill (or something similar) is available, it's much simpler to use that instead.

7 Comments

You didn't fix either of the big fat ugly uselessnesses in the OP's code. You really want ssh [email protected] <<<"ps -aux | awk '/\/srv\/adih\/server\/app\.js/ { print \$2 }'" (refactored to a here string to make it working code in a comment).
Something is not as what you said. The result I got in the first command is exactly the pid on remote machine.
$ ssh [email protected] ps -aux|grep -v \"grep\"|grep "/srv/adih/server/app.js"|awk '{print $2}' 6373 and I executed it on remote computer: $ ps aux | grep "/srv/adih/server/app.js" git 6373 0.0 0.4 1650012 37164 pts/1 Sl+ 13:40 0:00 /usr/local/bin/node --debug /srv/adih/server/app.js git 9224 0.0 0.0 118488 2204 pts/2 S+ 14:33 0:00 grep --color=auto /srv/adih/server/app.js
Yes yes, the PID is correct, but the grep and Awk are being run on your local host on the entire ps output from the remote server. (In most cases this is not really a problem -- if the output is huge, you probably want to avoid passing it over a thin remote pipe, and/or if your local system is really strapped on resources, it would be nice if all the filtering ran on the remote).
@tripleee I've been assuming that the idea is to use kill instead of echo (and that pkill isn't available), so that the "useless use of echo" is just a test case. But I'll edit to include more options (and a simplified pipeline).
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.