Assign a output of jps -vl command to a variable in shell script - linux

I need to assign the output of a command to a variable. The command I tried is:
#!/bin/bash
JAVA_PROCESSES=`jps -vl | grep -v 'sun.tools.jps.Jps' | grep -v 'hudson.remoting.jnlp.Main' | grep -v grep`
NUMBER_OF_JAVA_PROCESSES=`echo $JAVA_PROCESSES | wc -l`
echo $NUMBER_OF_JAVA_PROCESSES
echo $JAVA_PROCESSES
..
When I tried as in above, all java processes grepped are assigned to JAVA_PROCESSES variable in one line. Processes are not separated by new line. Therefore $NUMBER_OF_JAVA_PROCESSES always give 1 for me.
Also $NUMBER_OF_JAVA_PROCESSES show 1 even no processes are assigned to JAVA_PROCESSES due to the empty line in $JAVA_PROCESSES.
Please suggest a way to assign grepped processes separated by new line.

If the main thing you want is to know whether or not you got any at all, you could just test if the variable is empty:
java_procs=$(jps -vl | grep -v 'sun.tools.jps.Jps' | grep -v 'hudson.remoting.jnlp.Main' | grep -v grep)
if [ -z "$java_procs" ]; then
echo "No processes"
fi
Also, we can simplify the grep by using extended regex and just needing a single processes:
java_procs=$(jps -vl | grep -Ev 'sun.tools.jps.Jps|hudson.remoting.jnlp.Main|grep')
Assuming none of the lines output by jps can contain linebreaks themselves, we could get the count after that if we need it:
num_procs=$(printf '%s\n' "$java_procs" | wc -l)
The main problem you were running into is that you weren't quoting your variable, so echo $JAVA_PROCESSES was being expanded and then subject to word splitting, so your newlines were being "eaten" by the shell. You'd always have only one line which would be a space separated list of all the words in your JAVA_PROCESSES variable. To protect from word splitting you can quote the variable, as I did in my code above.
echo will also always add a line break at the end, which is good sometimes, and not so good sometimes, but you should be aware of it happening (that's why you would always get a count of 1 even when there were no processes).

Related

Assign output of command to environment variable different from original output (bash)

I have encountered a problem with a script I have originally designed.
I am trying to get the number of lines a command displays and if the number is bigger than a value, something should happen.
My problem is that originally this worked fine, now it doesn't.
In my script I am using the following command
NO_LINES=$(ps -ef | grep "sh monitor.sh" | wc -l)
echo $NO_LINES
echo $NO_LINES prints 0 even though it should print 1, the line for the grep command.
If I execute the command separately (not assigning the result to an environment variable) like this
ps -ef | grep "sh monitor.sh" | wc -l
This will print out 1 which is the correct result.
Why is it that by assigning the result to the variable, the value is lower with 1 than the original result?
The bash version of the machine is 4.3.46(1)-release.
Thanks

Echo output of a piped command

I am trying to just echo a command within my bash script code.
OVERRUN_ERRORS="$ifconfig | egrep -i "RX errors" | awk '{print $7}'"
echo ${OVERRUN_ERRORS}
however it gives me an error and the $7 does not show up in the command. I have to store it in a variable, because I will process the output (OVERRUN_ERRORS) at a later point in time. What's the right syntax for doing this? Thanks.
On Bash Syntax
foo="bar | baz"
...is assigning the string "bar | baz" to the variable named foo; it doesn't run bar | baz as a pipeline. To do that, you want to use command substitution, in either its modern $() syntax or antiquated backtick-based form:
foo="$(bar | baz)"
On Storing Code For Later Execution
Since your intent isn't clear in the question --
The correct way to store code is with a function, whereas the correct way to store output is in a string:
# store code in a function; this also works with pipelines
get_rx_errors() { cat /sys/class/net/"$1"/statistics/rx_errors; }
# store result of calling that function in a string
eth0_errors="$(get_rx_errors eth0)"
sleep 1 # wait a second for demonstration purposes, then...
# compare: echoing the stored value, vs calculating a new value
echo "One second ago, the number of rx errors was ${eth0_errors}"
etho "Right now, it is $(get_rx_errors eth0)"
See BashFAQ #50 for an extended discussion of the pitfalls of storing code in a string, and alternatives to same. Also relevant is BashFAQ #48, which describes in detail the security risks associated with a eval, which is often suggested as a workaround.
On Collecting Interface Error Counts
Don't use ifconfig, or grep, or awk for this at all -- just ask your kernel for the number you want:
#!/bin/bash
for device in /sys/class/net/*; do
[[ -e $device/statistics/rx_errors ]] || continue
rx_errors=$(<"${device}/statistics/rx_errors")
echo "Number of rx_errors for ${device##*/} is $rx_errors"
done
Use $(...) to capture the output of a command, not double quotes.
overrun_errors=$(ifconfig | egrep -i "RX errors" | awk '{print $7}')
Your double quotes around RX errors are a problem. Try;
OVERRUN_ERRORS="$ifconfig | egrep -i 'RX errors' | awk '{print $7}'"
To see the commands as they are executing, you can use
set -v
or
set -x
For example;
set -x
OVERRUN_ERRORS="$ifconfig | egrep -i 'RX errors' | awk '{print $7}'"
set +x

Mail output with Bash Script

SSH from Host A to a few hosts (only one listed below right now) using the SSH Key I generated and then go to a specific file, grep for a specific word with a date of yesterday .. then I want to email this output to myself.
It is sending an email but it is giving me the command as opposed to the output from the command.
#!/bin/bash
HOST="XXXXXXXXXXXXXXXXXX, XXXXXXXXXXXXX"
DATE=$(date -d "yesterday")
INVALID=' cat /xxx/xxx/xxxxx | grep 'WORD' | sed 's/$/.\n/g' | grep "$DATE"'
COUNT=$(echo "$INVALID" | wc -c)
for x in $HOSTS
do
ssh BLA#"$x" $COUNT
if [ "$COUNT" -gt 1 ];
then
EMAILTEXT=""
if [ "$COUNT" -gt 1 ];
then
EMAILTEXT="$INVALID"
fi
fi
done | echo -e "$EMAILTEXT" | mail XXXXXXXXXXX.com
This isn't properly an attempt to answer your question, but I think you should be aware of some fundamental problems with your code.
INVALID=' cat /xxx/xxx/xxxxx | grep 'WORD' | sed 's/$/.\n/g' | grep "$DATE"'
This assigns a simple string to the variable INVALID. Because of quoting issues, s/$/.\n/g is not quoted at all, and will probably be mangled by the shell. (You cannot nest single quotes -- the first single-quoted string extends from the first quote to the next one, and then WORD is outside of any quotes, followed by the next single-quoted string, etc.)
If your intent is to execute this as a command at this point, you are looking for a command substitution; with the multiple layers of uselessness peeled off, perhaps something like
INVALID=$(sed -n -e '/WORD/!d' -e "/$DATE/s/$/./p" /xxx/xxx/xxxx)
which looks for a line matching WORD and $DATE and prints the match with a dot appended at the end -- I believe that's what your code boils down to, but without further insights into what this code is supposed to do, it's impossible to tell if this is what you actually need.
COUNT=$(echo "$INVALID" | wc -c)
This assigns a number to $COUNT. With your static definition of INVALID, the number will always be 62; but I guess that's not actually what you want here.
for x in $HOSTS
do
ssh BLA#"$x" $COUNT
This attempts to execute that number as a command on a number of remote hosts (except the loop is over HOSTS and the variable containing the hosts is named just HOST). This cannot possibly be useful, unless you have a battery of commands named as natural numbers which do something useful on these remote hosts; but I think it's safe to assume that that is not what is supposed to be going on here (and if it was, it would absolutely be necessary to explain this in your question).
if [ "$COUNT" -gt 1 ];
then
EMAILTEXT=""
if [ "$COUNT" -gt 1 ];
then
EMAILTEXT="$INVALID"
fi
fi
So EMAILTEXT is either an empty string or the value of INVALID. You assigned it to be a static string above, which is probably the source of your immediate question. But even if it was somehow assigned to a command on the local host, why do you need to visit remote hosts and execute something there? Or is your intent actually to execute the command on each remote host and obtain the output?
done | echo -e "$EMAILTEXT" | mail XXXXXXXXXXX.com
Piping into echo makes no sense at all, because it does not read its standard input. You should probably just have a newline after done; though a possibly more useful arrangement would be to have your loop produce output which we then pipe to mail.
Purely speculatively, perhaps something like the following is what you actually want.
for host in $HOSTS; do
ssh BLA#"$host" sed -n -e '/WORD/!d' -e "/$DATE/s/$/./p" /xxx/xxx/xxxx |
grep . || echo INVALID
done | mail XXXXXXXXXXX.com
If you want to check that there is strictly more than one line of output (which is what the -gt 1 suggests) then this may need to be a little bit more complicated.
Your command substitution is not working. You should read up on how it works but here are the problem lines:
COUNT=$(echo "$INVALID" | wc -c)
[...]
ssh BLA#"$x" $COUNT
should be:
COUNT_CMD="'${INVALID} | wc -c'"
[...]
COUNT=$(ssh BLA#"$x" $COUNT_CMD)
This inserts the value of $INVALID into the string, and puts the whole thing in single quotes. The single quotes are necessary for the ssh call so the pipes aren't evaluated in the script but on the remote host. (COUNT is changed to COUNT_CMD for readability/clarity.)
EDIT:
I misread the question and have corrected my answer.

store command output to array in shell script

I'm using ssh to connect to a remote machine and read a log file there. From that log file, based on some tokens, I extract specific logs and store it in a variable. Every log is in new line in the log file and the data can contain any character including white space.
array=("$(egrep "UserComments/propagateBundle-2013-10-19--04:42:13|UserComments/propagateBundle-2013-10-19--04:38:36|UserComments/propagateBundle-2013-10-19--04:34:24" <path>/propagateBundle.log)")
echo ${array[0]}
echo "$array"
First echo prints complete output in one line separated by white space while the other prints outputs in new line. Problem, is, I'm not able to save this output as an array. I tried this:
newArray=("$array")
max=${#newArray[#]}
echo $max
But echoing 'max' yields '1' on the screen. How can I save the output in an array? I also tried using
IFS=\`\n`
but could not get the data in an array.
EDIT
I used the solution given by Anubhav and it worked like charm. Now I faced a second issue. Since my data contains white spaces, so the array broke at white spaces and wrongly contained the one comments as multiple arrays. So, I used
IFS=\`\n`
and also used a $ symbol before backticks. Although this solves my problem, I still get an exception in the logs:
test.sh: line 11: n: command not found
Any suggestions?
Don't put quotes in the command substitution:
array=( $(egrep "UserComments/propagateBundle-2013-10-19--04:42:13|UserComments/propagateBundle-2013-10-19--04:38:36|UserComments/propagateBundle-2013-10-19--04:34:24" <path>/propagateBundle.log) )
With quotes as in your code whole output is treated as single string in the array.
I've used IFS=('\n') otherwise all "n" chars disappears from results and sort command doesn't work properly. See bellow, it is a customized llq output.
#!/bin/bash
IFS=('\n')
raw=(`llq -f %id %o %gu %gl %st %BS %c`)
echo
echo ${raw[*]} | grep "step(s)"
echo
echo ${raw[*]} | grep "Step"
echo ${raw[*]} | grep "\---*"
echo ${raw[*]} | grep "bgp-fn*" | sort -k5 -r
echo ${raw[*]} | grep "\---*"
echo ${raw[*]} | grep "Step"
echo
echo ${raw[*]} | grep "step(s)"
echo

Error while comparing in shell

I am trying to search a pattern(trailer) and if it occures more than once in a file, I need those filenames displayed
for f in *.txt
do
if((tail -n 1 $f | grep '[9][9][9]*' | wc -l) -ge 2);
then
echo " The file $f has more than one trailer"
fi
done
Your most crying syntax error is that -ge is an operator for the [ … ] or [[ … ]] conditional construct. It doesn't have a chance the way you wrote the program. -ge needs a number on both sides, and what you have on the left is a command. You probably meant to have the output of the command, which would need the command substitution syntax: $(…). That's
if [ $(tail -n 1 $f | grep '[9][9][9]*' | wc -l) -ge 2 ]; then
This is syntactically correct but will never match. tail -n 1 $f outputs exactly one line (unless the file is empty), so grep sees at most one line, so wc -l prints either 0 or 1.
If you want to search the pattern on more than one line, change your tail invocation. While you're at it, you can change grep … | wc -l to grep -c; both do exactly the same thing, which is to count matching lines. For example, to search in the last 42 lines:
if [ $(tail -n 42 -- "$f" | grep -c '[9][9][9]*') -ge 2 ]; then
If you want to search for two matches on the last lines, that's different. grep won't help because it determines whether each line matches or not, it doesn't look for multiple matches per line. If you want to look for multiple non-overlapping matches on the last line, repeat the pattern, allowing arbitrary text in between. You're testing if the pattern is present or not, so you only need to test the return status of grep, you don't need its output (hence the -q option).
if tail -n 1 -- "$f" | grep -q '[9][9][9]*.*[9][9][9]*'; then
I changed the tail invocations to add -- in case a file name begins with - (otherwise, tail would interpret it as an option) and to have double quotes around the file name (in case it contains whitespace or \[*?). These are good habits to get into. Always put double quotes around variable substitutions "$foo" and command substitutions "$(foo)" unless you know that the substitution will result in a whitespace-separated list of glob patterns.
tail -n 1 $f will produce (at most) one line of output, which is fed to grep, which can then produce by definition at most one line of output, which means that the output of wc will never be more than 1, and will especially never be greater than 2. Aside from the syntax issues mentioned in other comments/answers, I think this logic is probably one of the core problems.

Resources