0

I am trying to execute my script, but the $ 1 argument is concatenated with the arguments of the last pipe, resulting in the following

killProcess(){   
 ps aux |grep $1 | tr -s " " " " | awk "{printf \"%s \",\$2}" | tr " " "\n" | xargs -l1 echo $1
}
$killProcess node
node 18780
node 965856
node 18801
node 909028
node 19000
node 1407472
node 19028
node 583620
node 837
node 14804
node 841
node 14260

but I just want the list of pids, without the node argument to be able to delete them, that only happens when I put it under a script, in command line it works normally for me because I don't pass any arguments to the script and it doesn't get concatenated.

6
  • Just to be sure... What do you expect the $1 to stand for in the find command at the end of the line? You know that it will be 'node', and not the first field on the line generated by awk? Commented Dec 25, 2019 at 5:49
  • Also, sounds like you may be trying to reimplemented the equivalent of the killall command... Commented Dec 25, 2019 at 5:50
  • I know, I'm doing it for learning purposes. Commented Dec 25, 2019 at 5:55
  • $ 1 I want the pid of each process thrown at me, only when I execute it under the script, the argument passed to the process is concatenated, with the argument that picks up xargs. , when I execute in the main shell, the argument is not concatenated as there is no $ 1 spent, everything was executed in the main shell. Commented Dec 25, 2019 at 5:57
  • Fair enough... Then replace the $1 in xargs by '{}' ';' (yes the syntax is weird...) Commented Dec 25, 2019 at 6:06

1 Answer 1

3

The immediate problem is that you don't want the $1 at the end. In that context, $1 expands to the first argument to the function ("node", in your example), which then gets passed to xargs and treated as part of the command it should execute. That is, the last part of the pipeline expands to:

xargs -l1 echo node

...so when xargs receives "18780" as input, it runs echo node 18780, which of course prints "node 18780".

Solution: remove the $1, making the command just xargs -l1 echo, so when xargs receives "18780" as input, it runs echo 18780, which prints just "18780".

That'll fix it, but there's also a huge amount of simplification that can be done here. Many elements of the pipe aren't doing anything useful, or are working at cross purposes with each other.

Start with the last command in the pipe, xargs. It's taking in PIDs, one per line, and printing them one per line. It's not really doing anything at all (that I can see anyway), so just leave it off. (Unless, of course, you actually want to use kill instead of echo -- in that case, leave it on.)

Now look at the next two commands from the end:

awk "{printf \"%s \",\$2}" | tr " " "\n"`

Here, awk is printing the PIDs with a space after each one, and then tr is turning the spaces into newlines. Why not just have awk print each one with a newline to begin with? You don't even need printf for this, you can just use print since it automatically adds a newline. It's also simpler to pass the script to awk in single-quotes, so you don't have to escape the double-quotes, dollar sign, and (maybe) backslash. So any of these would work:

awk "{printf \"%s\\n\",\$2}"
awk '{printf "%s\n",$2}'
awk '{print $2}'

Naturally, I recommend the last one.

Now, about the command before awk: tr -s " " " ". This "squeezes" runs of spaces into single spaces, but that's not needed since awk treats runs of spaces as (single) field delimiters. So, again, leave that command out.

At this point, we're down to the following pipeline:

ps aux | grep $1 | awk '{print $2}'

There are two more things I'd recommend here. First, you should (almost) always have double-quotes around shell variable, parameter, etc references like $1. So use grep "$1" instead.

But don't do that, because awk is perfectly capable of searching; there's no need for both grep and awk. In fact, awk can be much more precise, searching only a specific field instead of the whole line. The downside is, it is a bit more complex to do, but knowing how to make awk do more complex things is useful. The best way to let awk work with a shell variable or parameter is to use its -v option to create an awk variable with the same value, and use that. You can then use the ~ to check for a regex match to the variable. Something like this:

awk -v proc="$1" '$11 ~ proc {print $2}'

Note: I'm assuming you want to search for $1 in the executable name, and that that's the 11th field of ps aux on your system. Searching that field only will keep it from matching in e.g. the username (killing all of a user's processes because their name contains some program name isn't polite). You might actually want to be even more specific, so that e.g. trying to kill node doesn't accidentally kill nodemon as well; that'll be a matter of using more specific search patterns.

So, here's the final result:

killProcess(){
    ps aux | awk -v proc="$1" '$11 ~ proc {print $2}'
}

To actually kill the processes, add back xargs -l1 kill at the end.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your answer, I see that you put points that I had not taken into account, I solved it by putting an alias with xargs -l1 -I%
If I know, I had forgotten to clean the command, since I was also testing it with cut, I know that awk doesn't need to replace the spaces.
Thanks for your reply, I didn't know some awk functions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.