Set part of grep to variable - linux

mysqladmin proc status | grep "Threads"
Output:
Uptime: 2304 Threads: 14 Questions: 2652099 Slow queries: 0 Opens: 48791 Flush tables: 3 Open tables: 4000 Queries per second avg: 1151.08
I would like to set it so $mysqlthread would output 14 after running echo $mysqlthread

Probably the easiest way is with Perl instead of grep.
mysqladmin proc status | perl -nle'/Threads: (\d+)/ && print $1'
perl -n means "go through each line of input".
perl -l means "print a \n at the end of every print"
perl -e means "here is my program"
/Threads: (\d+)/ means "match Threads: followed by one or more digits. And print $1 means "print the digits I found as denoted by the parentheses around \d+.

Using grep
$ mysqlthread=$(mysqladmin proc status | grep -Po 'Threads: \K\d+')
$ echo "$mysqlthread"
14

There are many ways to solve this. This is one:
mysqladmin proc status | grep "Threads" | tr -s ' ' | cut -d' ' -f4
The tr command with flag -s is used to translate all multiple consecutive spaces into a single space. Then, cut command return the 4th field using a single space as delimiter.
The advantage of piping commands is that one can make this process interactively. And whenever you aren't sure which flag to use, the manual pages are there to help: man grep, man tr, man cut, etc.

Add awk to split the output,
mysqlthread=$(mysqladmin proc status | grep "Threads" | awk '{print $4}')

Related

Parse info from bash

I'm trying to parse out certain information from a bash script on Ubuntu
I'm having a bash script execute every x seconds which does write:
forever list
this response from that command looks like this:
info: Forever processes running
data: uid command script forever pid logfile uptime
data: [0] _1b2 /usr/bin/nodejs /home/ubuntu/node/server.js 28968 28970 /root/.forever/_1b2.log 0:0:17:17.233
I want to parse out the location of the logfile /root/.forever/_1b2.log
Any ideas how to accomplish this with bash?
Two of the many awk variations to solve this issue:
# most basic
command | awk 'NR==3{ print $8 }' data
# a little bit more robust:
command | awk '$1=="data:" && $2=="[0]" { print $8 }' data
# ^^^^^^^^^
# here I filter on the "[0]" text, but depending your needs
# you might want to use $3=="_1b2" or $4=="/usr/bin/nodejs"
You could try the below GNU and basic sed commands,
$ command | sed -nr 's/^.* (\S+) [0-9]+:[0-9]+\S+$/\1/p'
/root/.forever/_1b2.log
$ command | sed -n 's/^.* \(\S\+\) [0-9]\+:[0-9]\+\S\+$/\1/p'
/root/.forever/_1b2.log
It prints only the non-space characters which is followed by a space + one or more digits + : symbol + one or more digits.
You could grep the line(s) you're looking for and pipe the output to awk:
forever list | grep "\.log" | awk '{print $4}'
This will find all processes with a .log file, and print the log file's location (4th column).
Assuming the logfile is in /root/.forever directory, you can use grep this regex:
forever list | grep -o "/root/\.forever/.*\.log"

Shell script to get count of a variable from a single line output

How can I get the count of the # character from the following output. I had used tr command and extracted? I am curious to know what is the best way to do it? I mean other ways of doing the same thing.
{running_device,[test#01,test#02]},
My solution was:
echo '{running_device,[test#01,test#02]},' | tr ',' '\n' | grep '#' | wc -l
I think it is simpler to use:
echo '{running_device,[test#01,test#02]},' | tr -cd # | wc -c
This yields 2 for me (tested on Mac OS X 10.7.5). The -c option to tr means 'complement' (of the set of specified characters) and -d means 'delete', so that deletes every non-# character, and wc counts what's provided (no newline, so the line count is 0, but the character count is 2).
Nothing wrong with your approach. Here are a couple of other approaches:
echo $(echo {running_device,[test#01,test#02]}, |awk -F"#" '{print NF - 1}')
or
echo $((`echo {running_device,[test#01,test#02]} | sed 's+[^#]++g' | wc -c` - 1 ))
The only concern I would have is if you are running this command in a loop (e.g. once for every line in a large file). If that is the case, then execution time could be an issue as stringing together shell utilities incurs the overhead of launching processes which can be sloooow. If this is the case, then I would suggest writing a pure awk version to process the entire file.
Use GNU Grep to Avoid Character Translation
Here's another way to do this that I personally find more intuitive: extract just the matching characters with grep, then count grep's output lines. For example:
echo '{running_device,[test#01,test#02]},' |
fgrep --fixed-strings --only-matching # |
wc -l
yields 2 as the result.

selecting only required lines from unix shell prompt

Lets say I am running
$: ps au
in a shell prompt and want to select 2nd field of 5th entry in that, no matter which process it is. How do I do that ?
With awk.
awk 'NR==6 { print $2 }'
The 6th record because you need to skip the header.
If you don't want to use awk or the equivalent perl or ruby commands, you can also use more low-level tools:
ps au | head -6 | tail -1 | cut -d ' ' -f 2
In "ps au" output, second field is the process ID; you can extract it directly by telling to ps what you need:
ps a -o pid=
Then you just need to output the fifth line:
ps a -o pid= | sed '5!d'

linux shell scripting kiddie's question

an Unix shell script with only purpose - count the number of running processes of qmail (could be anything else). Easy thing, but there must be some bug in code:
#!/bin/bash
rows=`ps aux | grep qmail | wc -l`
echo $rows
Because
echo $rows
always shows greater number of rows (11) than if I just count rows in
ps aux | grep qmail
There are just 8 rows. Does it work this way on your system too?
Nowadays with linux, there is pgrep. If you have it on your system, you can skip grep -v grep
$ var=$(pgrep bash) # or `pgrep bash | wc -l`
$ echo $var
2110 2127 2144 2161 2178 2195 2212 2229
$ set -- $var; echo ${#}
8
also, if your ps command has -C option, another way
$ ps -C bash -o pid= | wc -l
if not, you can set a character class in your grep pattern
$ ps aux|grep [q]mail | wc -l
It appears that you're counting the grep process itself and the header line that ps normally prints before its output.
I'd suggest something more like:
qprocs=$(ps auxwww | grep -c "[q]mail")
... note that GNU grep has a "-c" switch to have it print a "count" of matches rather than the lines themselves. The trick with the regular expression here is to match qmail without matching the literal string that's on the grep command line. So we take any single character in the string and wrap it in square brackets such that it is a single character "class." The regexp: [q]mail matches the string qmail without matching the string [q]mail.
Note that even with this regex you may still find some false positive matches. If you really want to be more precise then you should supply a custom output format string to your ps command (see the man pages) or you should feed it through a pipemill or you should parse the output of the ps command based on fields (using awk or cut or a while read loop). (The -o option to ps is by far the easiest among these).
No, since I'm not running qmail. However, you will want to, at a bare minimum, exclude the process running your grep:
ps aux | grep qmail | grep -v grep
For debugging, you may want to do:
rows=`ps aux | grep qmail`
echo $rows >debug.input
od -xcb debug.input
(to see your input to the script in great detail) and then rewrite your script temporarily as:
#!/bin/bash
rows=`cat debug.input | wc -l`
echo $rows
That way, you can see the input and figure out what effect it's having on your code, even as you debug it.
A good debugger will eventually learn to only change one variable at a time. If your changing your code to get it working, that's the variable - don't let the input to your code change as well.
Use
$ /sbin/pidof qmail
A few ways...
ps -e | grep ' [q]mail' | wc -l
ps -C qmail -opid= | wc -l
pidof qmail | tr ' ' '\n' | wc -l
pgrep is on many Linux distributions, and I imagine available for other Unices.
[dan#khorium ~]$ whatis pgrep
pgrep (1) - look up or signal processes based on name and other attributes
[dan#khorium ~]$ pgrep mingetty
1920
1921
1922
1923
1924
In your case, pgrep qmail | wc -l should do the trick.

Split output of command by columns using Bash?

I want to do this:
run a command
capture the output
select a line
select a column of that line
Just as an example, let's say I want to get the command name from a $PID (please note this is just an example, I'm not suggesting this is the easiest way to get a command name from a process id - my real problem is with another command whose output format I can't control).
If I run ps I get:
PID TTY TIME CMD
11383 pts/1 00:00:00 bash
11771 pts/1 00:00:00 ps
Now I do ps | egrep 11383 and get
11383 pts/1 00:00:00 bash
Next step: ps | egrep 11383 | cut -d" " -f 4. Output is:
<absolutely nothing/>
The problem is that cut cuts the output by single spaces, and as ps adds some spaces between the 2nd and 3rd columns to keep some resemblance of a table, cut picks an empty string. Of course, I could use cut to select the 7th and not the 4th field, but how can I know, specially when the output is variable and unknown on beforehand.
One easy way is to add a pass of tr to squeeze any repeated field separators out:
$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
I think the simplest way is to use awk. Example:
$ echo "11383 pts/1 00:00:00 bash" | awk '{ print $4; }'
bash
Please note that the tr -s ' ' option will not remove any single leading spaces. If your column is right-aligned (as with ps pid)...
$ ps h -o pid,user -C ssh,sshd | tr -s " "
1543 root
19645 root
19731 root
Then cutting will result in a blank line for some of those fields if it is the first column:
$ <previous command> | cut -d ' ' -f1
19645
19731
Unless you precede it with a space, obviously
$ <command> | sed -e "s/.*/ &/" | tr -s " "
Now, for this particular case of pid numbers (not names), there is a function called pgrep:
$ pgrep ssh
Shell functions
However, in general it is actually still possible to use shell functions in a concise manner, because there is a neat thing about the read command:
$ <command> | while read a b; do echo $a; done
The first parameter to read, a, selects the first column, and if there is more, everything else will be put in b. As a result, you never need more variables than the number of your column +1.
So,
while read a b c d; do echo $c; done
will then output the 3rd column. As indicated in my comment...
A piped read will be executed in an environment that does not pass variables to the calling script.
out=$(ps whatever | { read a b c d; echo $c; })
arr=($(ps whatever | { read a b c d; echo $c $b; }))
echo ${arr[1]} # will output 'b'`
The Array Solution
So we then end up with the answer by #frayser which is to use the shell variable IFS which defaults to a space, to split the string into an array. It only works in Bash though. Dash and Ash do not support it. I have had a really hard time splitting a string into components in a Busybox thing. It is easy enough to get a single component (e.g. using awk) and then to repeat that for every parameter you need. But then you end up repeatedly calling awk on the same line, or repeatedly using a read block with echo on the same line. Which is not efficient or pretty. So you end up splitting using ${name%% *} and so on. Makes you yearn for some Python skills because in fact shell scripting is not a lot of fun anymore if half or more of the features you are accustomed to, are gone. But you can assume that even python would not be installed on such a system, and it wasn't ;-).
try
ps |&
while read -p first second third fourth etc ; do
if [[ $first == '11383' ]]
then
echo got: $fourth
fi
done
Your command
ps | egrep 11383 | cut -d" " -f 4
misses a tr -s to squeeze spaces, as unwind explains in his answer.
However, you maybe want to use awk, since it handles all of these actions in a single command:
ps | awk '/11383/ {print $4}'
This prints the 4th column in those lines containing 11383. If you want this to match 11383 if it appears in the beginning of the line, then you can say ps | awk '/^11383/ {print $4}'.
Using array variables
set $(ps | egrep "^11383 "); echo $4
or
A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
Similar to brianegge's awk solution, here is the Perl equivalent:
ps | egrep 11383 | perl -lane 'print $F[3]'
-a enables autosplit mode, which populates the #F array with the column data.
Use -F, if your data is comma-delimited, rather than space-delimited.
Field 3 is printed since Perl starts counting from 0 rather than 1
Getting the correct line (example for line no. 6) is done with head and tail and the correct word (word no. 4) can be captured with awk:
command|head -n 6|tail -n 1|awk '{print $4}'
Instead of doing all these greps and stuff, I'd advise you to use ps capabilities of changing output format.
ps -o cmd= -p 12345
You get the cmmand line of a process with the pid specified and nothing else.
This is POSIX-conformant and may be thus considered portable.
Bash's set will parse all output into position parameters.
For instance, with set $(free -h) command, echo $7 will show "Mem:"

Resources