简体   繁体   中英

bash script concatenates input arguments with pipe xargs arguments

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.

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 eg 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 eg 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM