basename or endswith in awk? - bash

I'm using awk to get the pid of a specific process by name.
#!/bin/sh
for pid in $(ps | awk '$4 == "foo" { print $1 }')
do
i=1
echo "killing foo at pid $pid"
kill $pid && echo 'ok' || echo 'failed'
done
where ps output on OSX is something like:
$ ps
PID TTY TIME CMD
1858 ttys000 0:00.15 -bash
4148 ttys000 0:01.37 /a/b/c/foo
but my shell script only works if the process in the CMD column is exactly foo (no absolute paths).
I'm thinking I can use basename, but how do I call it from awk?
OR
Is there something like $4.endswith('foo') in awk?

awk can use regular expressions. So rather than $4 == "foo", you could do something like:
awk '$4 ~ /\/foo$/ { print $1 }'
The regular expression is between the / and /. \/foo$ says "a backslash followed by foo and then the end of the field." In this case, your field is /a/b/c/foo, so it would match.

In addition to regular expressions, you can use string manipulation as substr($4, length($4) - 2) == "foo".

First of all, you should use a while read construct instead of for. With a for loop, you have to wait for the command in the $(...) to execute before the for loop can start. Plus, you could overrun the command buffer. Not a major issue today, but when you run into it, you'll never get any indication that something went wrong:
ps | awk '$4 == "foo" { print $1 }' | while read pid
do
i=1
echo "killing foo at pid $pid"
kill $pid && echo 'ok' || echo 'failed'
done
Nothing much to do with your problem, but I need to get that off my chest.
Now on to your Answer:
The awk automatically does a loop, so since you're using awk anyway, why not let it do the work for you? Most awk implementations have a system command, so you can execute the kill right inside your awk script.
Also, take a look at man ps and check your PS options. Most ps command take the -o option which allows you to specify which fields to print. My ps command (which just happens to be on OS X) allows you to use ucomm which is just the name of the command without the directory or the command line parameters. Sounds useful for your situation. I'll use ps -opid, ucomm which will print only two columns for the ps command: The PID, and the command name sans directory and command line parameters:
$ ps -o pid,ucomm
PID UCOMM
1 launchd
10 kextd
11 UserEventAgent
12 mDNSResponder
13 opendirectoryd
14 notifyd
15 fseventsd
16 configd
17 diskarbitrationd
18 syslogd
(Just one warning, it looks like ucomm cuts things off at the 14th or 16th column. Just to let you know).
For awk, we can use the -v parameter to define an Awk variable. This is useful in case you write a shell script, and want awk to take the name of the program from the command parameters.
In Awk, as you know, $1 will represent the PID and $2 will represent the command sans the directory or command line parameters.
We can use the $2 == command to filter out all the lines in your ps command where the command is foo. This is a shortcut to the if statement.
And, most implementations of awk have a system function which can be used to run commands like kill from inside your awk script. Looks like we have almost everything we need:
ps -o pid,ucomm | awk -v command="foo" '$2 == command {system("kill " $1)}'
That's a nice clean one liner, but it doesn't check the status of the system command, nor does it echo out when it fails or what command and PID you're killing.
Good thing that Awk isn't just an abstruse command. It's also an abstruse programming language. We could test the return of the system command and add a few print statements. I didn't test this, but it should be pretty close:
ps -o pid,ucomm | awk -v command="foo" '$2 == command {
print "Killing " $2 " at PID " $1
if (system("kill " $1)) {
print "Failed to kill PID " $1
}
}'
The if may have to be if (! system("kill " $1)) { instead.

For the condition, you can use
$4 ~ /\/foo$/
which translates to "the 4th field matches an (escaped) forward-slash followed by 'foo' at the end of the line."

It seems you are trying to kill all processes which has name 'foo', how about:
killall foo

When looking for pids, you must exclude the awk process, otherwise it could kill the search process instead.
kill $(ps -aef | awk -v program="mplayer" '$0 ~ program {if ($0 !~ "awk") print $2}')

Related

how can you use awk's positional args in a shell script

I use the following snippet to kill abruptly any java process running:
ps -ef | grep java | grep -v grep | awk "{print $2}" | xargs kill -9
I would like to have this in a shell script that I can run on multiple machines but when I put it in the script and run it it takes the $2 in the awk as a passed in argument. I tried single, double and triple backslashing the $ but nothing works.
Single escape results in:
can't read "2": no such variable
Double escape results in:
awk: cmd. line:1 {print
awk: cmd. line:1: ^ unexpected newline or end of string
Triple escape results in:
awk: cmd. line:1 {print
awk: cmd. line:1: ^ unexpected newline or end of string
So I'm looking for a way to pass the $ arguments awk uses into a shell script
Could you please try following(taken inspiration from #James fine answer here).
ps -ef | awk '/[j]ava/{cmd=(cmd?cmd OFS:"")$2} END{print "kill -9 " cmd}'
This will only print the kill command and once you are happy with its results(pid numbers etc) then run following command to actually kill pids.
ps -ef | awk '/[j]ava/{cmd=(cmd?cmd OFS:"")$2} END{print "kill -9 " cmd}' | bash
How about:
$ ps -ef | awk '/[j]ava/{print $2}'
Sample output:
28510
There's a command that does this:
pkill java
pkill accepts the -signal (e.g. -9) notation just like kill, but I recommend starting with weaker signals to afford time for the killed processes to shut down properly. I usually do -HUP (hangup, -1) then standard (no flag for terminate, -TERM or -15) then -9 (kill, -KILL).
If you want to know what that'll do before running it, you can list the matching process IDs with:
pgrep java
or for more details:
pgrep -a java
(or pgrep java |xargs ps -f if you prefer)

Using ps and awk to get pid, then killing it

I have written a very short script that gets pid of $1 then kills the process. I have a few questions / requests for improvement. (OSX)
#!/bin/bash
getpid=$(ps aux -O start | grep -i "$1" | grep -v grep | grep -v $0 | awk '{ if (NR==1) {print $2}}')
kill $getpid
For example:
$ ~/killpro.sh java
Which kills the most recent instance of java.
Or:
$ ~/killpro.sh chrome
To kill Google Chrome, obviously.
First, is there any practical way to get this strung together into a single line, using read -p perhaps, so that it can in turn be aliased and become something like:
$ killpro
$ Enter name: chrome
$
Not that it's significantly easier, in fact it's slightly more work, but it bypasses the need to type a path and shell extension which is desirable.
Second, is there anything fundamentally wrong with my initial approach / am I missing something obvious that negates the need for any this?
Any feedback is appreciated.
Thanks in advance, I'm (as you can tell) new to this.
My advice is to use pkill instead. From the manual page:
NAME
pgrep, pkill -- find or signal processes by name
Most scripts with grep | awk are a useless use of grep and should be refactored to use just Awk.
#!/bin/bash
# Maybe add a case for $# > 1 and quit with an error message
# Or maybe loop over arguments if there is more than one
case $# in 0) read -p "Process name? " proc;; *) proc=$1;; esac
# Maybe your ps aux doesn't have the command in $11 -- adapt
kill $(ps aux -O start |
awk -v name="$proc" 'NR>1 && tolower($11) ~ tolower(name) {print $2}')
Your comments indicate you seek to kill the "latest" process (by which metric?) but your code, and this code, attempts to kill all the instances. If you want the most recently started process, the PID is not a good indicator; you should look at process start times etc. The output of ps is heavily system-dependent so you should probably post a separate question with more details about your OS if you need help figuring that out.
This particular problem regularly gets beaten to death several times a month here, so you should probably look at other implementations for inspiration. My recommendation would be "don't do that" but since you already did, I suppose it makes sense to point out the flaws and attempt to solidify the code. But if you don't have a compelling reason why you cannot install pkill, you should probably just use that (and if you do, using a properly debugged and tested script is way better than writing your own from scratch).
You could just use awk alone with getline to call out to ps and get the data you want:
awk -vv_kp="bash" 'BEGIN{ cmd="ps -axu -O T | grep "v_kp" |head -1"; cmd | getline var; close(cmd); split(var,var2," ");print var2[2];}'
or less awk, more shell:
awk -vv_kp="bash" 'BEGIN{ cmd="ps -axu -O T | grep "v_kp" |head -1|cut -d\" \" -f2"; cmd | getline var; close(cmd); print var;}'

How do i store the output of a bash command in a variable? [duplicate]

This question already has answers here:
How do I set a variable to the output of a command in Bash?
(15 answers)
Closed 8 years ago.
I'm trying to write a simple script for killing a process. I've already read Find and kill a process in one line using bash and regex so please don't redirect me to that.
This is my code:
LINE=$(ps aux | grep '$1')
PROCESS=$LINE | awk '{print $2}'
echo $PROCESS
kill -9 $PROCESS
I want to be able to run something like
sh kill_proc.sh node and have it run
kill -9 node
But instead what I get is
kill_process.sh: line 2: User: command not found
I found out that when I log $PROCESS it is empty.
Does anyone know what I'm doing wrong?
PROCESS=$(echo "$LINE" | awk '{print $2}')
or
PROCESS=$(ps aux | grep "$1" | awk '{print $2}')
I don't know why you're getting the error you quoted. I can't reproduce it. When you say this:
PROCESS=$LINE | awk '{print $2}'
the shell expands it to something like this:
PROCESS='mayoff 10732 ...' | awk '{print $2}'
(I've shortened the value of $LINE to make the example readable.)
The first subcommand of the pipeline sets variable PROCESS; this variable-setting command has no output so awk reads EOF immediately and prints nothing. And since each subcommand of the pipeline runs in a subshell, the setting of PROCESS takes place only in a subshell, not in the parent shell running the script, so PROCESS is still not set for later commands in your script.
(Note that some versions of bash can run the last subcommand of the pipeline in the current shell instead of in a subshell, but that doesn't affect this example.)
Instead of setting PROCESS in a subshell and feeding nothing to awk on standard input, you want to feed the value of LINE to awk and store the result in PROCESS in the current shell. So you need to run a command that writes the value of LINE to its standard output, and connects that standard output to the standard input of awk. The echo command can do this (or the printf command, as chepner pointed out in his answer).
You need to use echo (or printf) to actually put the value of $LINE onto the standard input of the awk command.
LINE=$(ps aux | grep "$1")
PROCESS=$(echo "$LINE" | awk '{print $2}')
echo $PROCESS
kill -9 $PROCESS
There's no need use LINE; you can set PROCESS with a single line
PROCESS=$(ps aux | grep "$1" | awk '{print $2}')
or better, skip the grep:
PROCESS=$(ps aux | awk -v pname="$1" '$1 ~ pname {print $2}')
Finally, don't use kill -9; that's a last resort for debugging faulty programs. For any program that you didn't write yourself, kill "$PROCESS" should be sufficient.

How to get process id from process name?

I'm trying to create a shell script getting the process id of the Skype app on my Mac.
ps -clx | grep 'Skype' | awk '{print $2}' | head -1
The above is working fine, but there are two problems:
1)
The grep command would get all process if their name just contains "Skype". How can I ensure that it only get the result, if the process name is exactly Skype?
2)
I would like to make a shell script from this, which can be used from the terminal but the process name should be an argument of this script:
#!/bin/sh
ps -clx | grep '$1' | awk '{print $2}' | head -1
This isn't returning anything. I think this is because the $2 in the awk is treated as an argument too. How can I solve this?
Your ps -cl1 output looks like this:
UID PID PPID F CPU PRI NI SZ RSS WCHAN S ADDR TTY TIME CMD
501 185 172 104 0 31 0 2453272 1728 - S ffffff80145c5ec0 ?? 0:00.00 httpd
501 303 1 80004004 0 31 0 2456440 1656 - Ss ffffff8015131300 ?? 0:11.78 launchd
501 307 303 4004 0 33 0 2453456 7640 - S ffffff8015130a80 ?? 0:46.17 distnoted
501 323 303 40004004 0 33 0 2480640 9156 - S ffffff80145c4dc0 ?? 0:03.29 UserEventAgent
Thus, the last entry in each line is your command. That means you can use the full power of regular expressions to help you.
The $ in a regular expression means the end of the string, thus, you could use $ to specify that not only does the output must have Skype in it, it must end with Skype. This means if you have a command called Skype Controller, you won't pull it up:
ps -clx | grep 'Skype$' | awk '{print $2}' | head -1
You can also simplify things by using the ps -o format to just pull up the columns you want:
ps -eo pid,comm | grep 'Skype$' | awk '{print $1}' | head -1
And, you can eliminate head by simply using awk's ability to select your line for you. In awk, NR is your record number. Thus you could do this:
ps -eo pid,comm | grep 'Skype$' | awk 'NR == 1 {print $1}'
Heck, now that I think of it, we could eliminate the grep too:
ps -eo pid,comm | awk '/Skype$/ {print $1; exit}'
This is using awk's ability to use regular expressions. If the line contains the regular expression, 'Skype$', it will print the first column, then exit
The only problem is that if you had a command Foo Skype, this will also pick it up. To eliminate that, you'll have to do a bit more fancy footwork:
ps -eo pid,comm | while read pid command
do
if [[ "$command" = "Skype" ]]
then
echo $pid
break
fi
done
The while read is reading two variables. The trick is that read uses white space to divide the variables it reads in. However, since there are only two variables, the last one will contain the rest of the entire line. Thus if the command is Skype Controller, the entire command will be put into $command even though there's a space in it.
Now, we don't have to use a regular expression. We can compare the command with an equality.
This is longer to type in, but you're actually using fewer commands and less piping. Remember awk is looping through each line. All you're doing here is making it more explicit. In the end, this is actually much more efficient that what you originally had.
If pgrep is available on Mac, you can use pgrep '^Skype$'. This will list the process id of all processes called Skype.
You used the wrong quotes in your script:
ps -clx | grep "$1" | awk '{print $2}' | head -1
or
pgrep "^$1$"
The problem with your second example is that the $1 is in single quotes, which prevents bash from expanding the variable. There is already a utility that accomplishes what you want without manually parsing ps output.
pgrep "$1"
You can do this in AppleScript:
tell application "System Events"
set skypeProcess to the process "Skype"
set pid to the unix id of skypeProcess
pid
end tell
which means you can use 'osascript' to get the PID from within a shell script:
$ osascript -e "tell application \"System Events\"" -e "set skypeProcess to the process \"Skype\"" -e "set pid to the unix id of skypeProcess" -e "pid" -e "end tell"
3873
You can format the output of ps using the -o [field],... and list by process name using -C [command_name] ;however, ps will still print the column header, which can be removed by piping it through grep -v PID
ps -o pid -C "$1" |grep -v PID
where $1 would be the command name (in this case Skype)
I'd so something like:
ps aux | grep Skype | awk 'NR==1 {print $2}'
==== UPDATE ====
Use the parameter without quotes and use single quotes for awk
#!/bin/bash
ps aux | grep $1 | awk 'NR==1 {print $2}'
Method 1 - Use awk
I don't see any reason to use the -l flag (long format), I also don't see any reason to use grep and awk at the same time: awk has grep capability built in. Here is my plan: use ps and output just 2 columns: pid and command, then use awk to pick out what you want:
ps -cx -o pid,command | awk '$2 == "Skype" { print $1 }'
Method 2 - Use bash
This method has the advantage that if you already script in bash, you don't even need awk, which save one process. The solution is longer than the other method, but very straight forward.
#!/bin/bash
ps -cx -o pid,command | {
while read pid command
do
if [ "_$command" = "_$1" ]
then
# Do something with the pid
echo Found: pid=$pid, command=$command
break
fi
done
}
pgrep myAwesomeAppName
This works great under Catalina 10.15.2
Use double quotes to allow bash to perform variable substitution.
Single quotes disable bash variable substitution mechanism.
ps -clx | grep "$1" | awk "{print $2}" | head -1

Bash/Awk: How can I run a command using bash or awk

How can I run a command in bash, read the output it returns and check if there's the text "xyz" in there in order to decide if I run another command or not?
Is it easy?
Thanks
if COMMAND | grep -q xyz; then
#do something
fi
EDIT: Made it quiet.
For example:
command1 | grep "xyz" >/dev/null 2>&1 && command2
run command1
its output filer with grep
discard output from the grep
and if the grep was successful (so found the string)
execute the command2
You can pipe the output of the command to grep or grep -e.
Your specification is very loose, but here is an idea to try
output="$(cmd args ....)"
case "${output}" in
*targetText* ) otherCommand args ... ;;
*target2Text* ) other2Command ... ;;
esac
I hope this helps.
While you can accomplish this task many different ways, this is a perfect use for awk.
prv cmd | awk '/xyx/ {print "cmd" } ' | bash
this is what you want
for example,
i have a text file called temp.txt that only contains 'xyz'
If i run the following command I will exepct the output "found it"
$ cat temp.txt | awk '/xyz/ {print "echo found it"}' | bash
> found it
so what I am doing is piping the output of my previous command into awk, who is looking for the pattern xyz (/xyz/). awk will print the command, in this case echo found it, and pipe it to bash to execute them. simple one-liner doing what you asked. note you can customize the regex that awk looks for.

Resources