Basic bash script issue - bash

Here is my first post on the forum, let me know if I may be more descriptif.
Yesterday, I script a lil' script to start, restart and stop my server in one, so I test 3 times the $1 argument to know if it's start, restart, stop string.
I take all the improvments if I can do it in another way :)
Here is my code :
#!/bin/bash
STA="start"
RES="restart"
STO="stop"
SERVERNAME="server_live"
if [ $1 -ge 1 ]
then
echo "Entre un argument : start, stop, restart"
elif [ $1 = $STA ]
then
screen -mdS $SERVERNAME
screen -S $SERVERNAME -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur redémarré"
elif [ $1 = $RES ]
then
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
screen -mdS $SERVERNAME
screen -S $SERVERNAME -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur Restart"
elif [ $1 = $STO ]
then
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
echo "Serveur Stoppé"
fi
I got the following error :
My code here
It means : syntaxe error near inexpected symbol elif line 10
Thanks in advance.. I wanna add that
#!/bin/bash
echo "read smthg"
read name
put an error too (on read) , how might I know if I got a version issue or something like that ?

you should use case instead of if ... elif, and (almost) always quote your variables between " " :
Please try this version (close to yours, but using case...esac and with some quotes added):
#!/bin/bash
SERVERNAME="server_live"
case "$1" in
start)
screen -mdS "$SERVERNAME"
screen -S "$SERVERNAME" -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur redémarré"
;;
restart)
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
screen -mdS "$SERVERNAME"
screen -S "$SERVERNAME" -dm bash -c 'sleep 1;cd /home/cfx-server; bash run.sh;exec sh'
echo "Serveur Restart"
;;
stop)
screen -ls | grep $SERVERNAME | cut -d. -f1 | awk '{print $1}' | xargs kill
echo "Serveur Stoppé"
;;
*)
echo "argument: '$1' non reconnu..."
exit 1
;;
esac
If you encounter an error, your script may contain things before that part that interfere? Please first paste your script in : www.shellcheck.net and see what it tells you (it will parse it and show a lot of common error, such as unclosed quotes, etc).
EDIT
The problem came from the fact that person was editing under windows (which uses 'CR-LF' line endings) and using it in linux/unix (which expected just LF, and thus took the CR as an additionnal character in the lines containing them).
To get rid of "everything non-printable" that isn't used for scripting in your script:
LC_ALL="C" tr -cd "[$(printf '\t')\$(printf '\n') -~]" <script.bash >script_without_nonprintables.bash
# LC_ALL="C" just before tr makes tr use that environment, which gives "ascii" instead of whatever locale you use. This helps ensure the ranges given are the ascii ones, and not something else.
# -c = complement, ie "whatever is not..." -d="delete", so -cd= "delete whatever is not specified"
# [a-d] = from a to d in the current locale (ascii, here, thanks to LC_ALL="C", so it will be : a, b, c or d
# in ascii, SPACE to ~ covers all the character you need to write scripts, except for TAB (\t) and Newline (\n), so I added those as well.
# for newline, I preceded it with an "\" to have it taken literally
chmod +x script_without_nonprintables.bash

Related

Bash capture a keystroke from another terminal

Using bash 4.3 and root powers, I want to be able to detect one keystroke (any key) from another terminal.
In terminal 1, a background process that writes to a named pipe once one character was read from another tty, say pts/18
read -rsn1 < /dev/pts/18 && echo > ./myfifo &
In terminal 2, an attempt to read a character from the same fifo
read -rsn1 < ./myfifo
It works moderately well, but only after several (3 or 4) keystrokes in pts/18, not the first one.
The mechanism appears to be redundant but it allows to start several background processes with different ttys and redirect to the same named pipe.
Hope you can help me.
One day I've tried to do that.
Wrote script who getting that you want with strace:
#!/bin/bash
# Couple of special symbols
BACKSPACE='\10\33[K'
BACKSPACE_EMPTY='\7'
BACKSLASH='\\'
QUOTE="\'"
DOUBLE_QUOUTE='\"'
LARROW='\10'
RARROW='\33[C'
#APPOSTRO="'\`'"
BACKSPACE_='\\33\[1P'
#LOGDATE=`date '+%Y/%m/%d %H:%M:%S'`
BADBIN='\\33\]'
while read -r line
do
# Avoiding binary symbols
NOBINline=$(echo "${line}" | strings)
# Current `pwd` value for session
CURRENT_PWD_OF_PID=$(readlink -f /proc/${1}/cwd)
# Getting username
USER_OF_PID=$(cat /proc/${1}/environ | strings | tr '.' ' ' | grep USER | awk -F= '{print $2}')
# Not the best but working way to create prompt line
HOSTNAME_OF_PID=`hostname -a`
STR_TO_REMOVE=$(printf "${USER_OF_PID}""#""${HOSTNAME_OF_PID}"":""${CURRENT_PWD_OF_PID}")
# Cut useless symbols from strace output.
parsed_line=$(echo "${NOBINline}" | perl -nale 'print $1 if ~/\"(.*)\"/gi')
if [ "${parsed_line}" == "\n" ]
then
parsed_line="{{ENTER}}"
fi
output_line=''
inchar_line=''
postinchar_line=''
inchar_line=$(printf "${parsed_line}")
if [ "${inchar_line}" == "{{ENTER}}" ]
then
echo ""
else
output_line=$(printf "${output_line}""${inchar_line}")
fi
if [ "${output_line}" != "${CURRENT_PWD_OF_PID}" -a "${output_line}" != "${STR_TO_REMOVE}" -a `echo "${NOBINline}" | grep -c "${BADBIN}"` -eq 0 ]
then
printf "${output_line}"
fi
done < <(sudo strace -e trace=write -s1000 -p $1 2>/dev/stdout)
How to use:
./script.sh <PID_of_mointored_console>
Also I've written the second script (much more ugly than first):
#!/bin/bash
while [ 1 ]
do
# lets and log_start -- names of two my scripts.
for PIDs in `ps awwux | grep [p]ts | grep bash | grep -v lets | grep -v log_start | grep -v grep | awk '{print $2}'`
do
PTS=$(ps awwux | grep "${PIDs}" | grep -v grep | awk '{print $7}')
if [ -f /tmp/bash_log.${PIDs} ]
then
sleep 1
else
touch /tmp/bash_log.${PIDs}
# lets.sh -- first script.
/bin/bash /root/lets.sh $PIDs >> /tmp/bash_log.${PIDs} &
fi
# tr -cd '\11\12\15\40-\176' < /tmp/x_bash_log.${PIDs} | sed 's/\]0;//g' | sed 's/\[C//g' > /tmp/bash_log.${PIDs}
done
for IFEMPTY in `find /tmp/ -type f -name 'bash_log*' -mmin +600`
do
if [ `cat "${IFEMPTY}" | grep -v "\'" | wc -c` -lt 2 ]
then
rm -rf "${IFEMPTY}"
else
sleep 1
fi
done
done
Start it in the background, place near first script.
It will catching all new terminal sessions, and start firstscript.sh <PID> for each, and creating /tmp/bash_log.<PID> file.
How to use:
./monitor.sh &
# Start new terminal
[sahaquiel#sahaquiel-PC ~]$ echo $$
916
[sahaquiel#sahaquiel-PC ~]$ HELO, IM A NEW STRING
bash: HELO,: command not found
[sahaquiel#sahaquiel-PC ~]$ tail -n3 /tmp/bash_log.916
916
''[sahaquiel#sahaquiel-PC ~]$ HELO, IM A NEW STRING
'''[sahaquiel#sahaquiel-PC ~]$ tail -n3 /tmp/bash_log.916
NB. I'm still working with it, created just for fun.
/tmp/bash_log files have awful formatting. I've also tried to log in the file backslashes, Ctrl+R, Ctrl+C and the same, but at now it's looks like not readable enough:
'''[sahaquiel#sahaquiel-PC ~]$ I WILL TYPE BACKSLASH THREE TIMES IN THE END OF THIS STRING 123[BACKSPACE] [Backspace found. Rewritten string]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ I WILL TYPE BACKSLASH THREE TIMES IN THE END OF THIS STRING 12[BACKSPACE] [Backspace found. Rewritten string]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ I WILL TYPE BACKSLASH THREE TIMES IN THE END OF THIS STRING 1[BACKSPACE] [Backspace found. Rewritten string]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ I WILL TYPE BACKSLASH THREE TIMES IN THE END OF THIS STRING
'''[sahaquiel#sahaquiel-PC ~]$ NOW I I[BACKSPACE] [Backspace found. Rewritten string]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ NOW I WILL PRESS \"LEFT ARROW\" THREE TIMES[LARROW] [Left arrow found.]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ NOW I WILL PRESS \"LEFT ARROW\" THREE TIME[LARROW]S[LARROW] [Left arrow found.]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ NOW I WILL PRESS \"LEFT ARROW\" THREE TIM[LARROW]ES[LARROW] [Left arrow found.]: └────>
'''[sahaquiel#sahaquiel-PC ~]$ NOW I WILL PRESS \"LEFT ARROW\" THREE TI[LARROW]MES
You can change it somehow you want.
There are a lot of variables, I wanted to use them later for extended strings parse way, but it doesn't work now correctly.
P.S. If someone really interested in that, can check this project on my Github (check profile for link)

Execute commands from shell script fails

I'm trying read a file which contains lines like this:
Run COMMAND with options "OPTIONS" and arguments "ARGUMENTS"
Then I want to execute this command with given options and arguments. For example I'd like to execute these commands:
Run pwd with options "" and arguments ""
Run ls with options "-al" and arguments "$HOME"
Run ls with options "-al" and arguments "Example: \"strange folder name\""
This is my code
#!/bin/bash
while read -r line
do
COMMAND=$(echo "$line" | cut -d" " -f 2)
OPTIONS=$(echo "$line" | cut -d" " -f 5 | tr -d '"')
ARGUMENTS=$(echo "$line" | cut -d" " -f 8)
$COMMAND $OPTIONS $ARGUMENTS
done <$1
First example is working as it should, second one is giving me error ls: cannot access $HOME: No such file or directory' and third one is not storing the name of the folder to $ARGUMENTS correctly.
second one is giving me error ls: cannot access $HOME: No such file or directory'
This is because the folder named $HOME does not exist. I am not talking about the value of $HOME variable, but the string literal. The shell does not execute the parameter expansion in your situation.
third one is not storing the name of the folder to $ARGUMENTS correctly
This is because -f 8 only extract column 8, try -f 8- to extract the 8th column and all the others until the end of line.
You can give a try to this version below:
while read -r line; do
COMMAND=$(printf "%s" "${line}" | cut -d" " -f 2)
OPTIONS=$(printf "%s" "${line}" | cut -d" " -f 5 | tr -d '"')
ARGUMENTS=$(printf "%s" "${line}" | cut -d" " -f 8-)
$COMMAND $OPTIONS "$(eval printf \"%s\" "$ARGUMENTS")"
done < "${1}"
The eval is a shell built-in command which is used to enable parameter expansion of ARGUMENTS, if applicable.
I have to warn you that the eval is usualy say risky to use.

bash: sed: unexpected behavior: displays everything

I wrote what I thought was a quick script I could run on a bunch of machines. Instead it print what looks like might be directory contents in a recursive search:
version=$(mysql Varnish -B --skip-column-names -e "SELECT value FROM sys_param WHERE param='PatchLevel'" | sed -n 's/^.*\([0-9]\.[0-9]*\).*$/\1/p')
if [[ $(echo "if($version == 6.10) { print 1; } else { print 0; }" | bc) -eq 1 ]]; then
status=$(dpkg-query -l | awk '{print $2}' | grep 'sg-status-polling');
cons=$(dpkg-query -l | awk '{print $2}' | grep 'sg-consolidated-poller');
if [[ "$status" != "" && "$cons" != "" ]]; then
echo "about to change /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm"; echo;
cp /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm.bkup;
sed -ir '184s!\x91\x93!\x91\x27--timeout=35\x27\x93!' /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm;
sed -n 183,185p /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm; echo;
else
echo "packages not found. Assumed to be not applicable";
fi
else
echo "This is 4.$version, skipping";
fi
The script is supposed to make sure Varnish is version 4.6.10 and has 2 custom .deb packages installed (not through apt-get). then makes a backup and edits a single line in a perl module from [] to ['--timeout=35']
it looks like its tripping up on the sed replace one liner.
There are two major problems (minor ones addressed in comments). The first is that you use the decimal code for [] instead of the hexa, so you should use \x5b\x5d instead of \x91\x93. The second problem is that if you do use the proper codes, sed will still interpret those syntactically as []. So you can't escape escaping. Here's what you should call:
sed -ri'.bkup' '184s!\[\]![\x27--timeout=35\x27]!' /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm
And this will create the backup for you (but you should double check).

A script to find all the users who are executing a specific program

I've written the bash script (searchuser) which should display all the users who are executing a specific program or a script (at least a bash script). But when searching for scripts fails because the command the SO is executing is something like bash scriptname.
This script acts parsing the ps command output, it search for all the occurrences of the specified program name, extracts the user and the program name, verifies if the program name is that we're searching for and if it's it displays the relevant information (in this case the user name and the program name, might be better to output also the PID, but that is quite simple). The verification is accomplished to reject all lines containing program names which contain the name of the program but they're not the program we are searching for; if we're searching gedit we don't desire to find sgedit or gedits.
Other issues I've are:
I would like to avoid the use of a tmp file.
I would like to be not tied to GNU extensions.
The script has to be executed as:
root# searchuser programname <invio>
The script searchuser is the following:
#!/bin/bash
i=0
search=$1
tmp=`mktemp`
ps -aux | tr -s ' ' | grep "$search" > $tmp
while read fileline
do
user=`echo "$fileline" | cut -f1 -d' '`
prg=`echo "$fileline" | cut -f11 -d' '`
prg=`basename "$prg"`
if [ "$prg" = "$search" ]; then
echo "$user - $prg"
i=`expr $i + 1`
fi
done < $tmp
if [ $i = 0 ]; then
echo "No users are executing $search"
fi
rm $tmp
exit $i
Have you suggestion about to solve these issues?
One approach might looks like such:
IFS=$'\n' read -r -d '' -a pids < <(pgrep -x -- "$1"; printf '\0')
if (( ! ${#pids[#]} )); then
echo "No users are executing $1"
fi
for pid in "${pids[#]}"; do
# build a more accurate command line than the one ps emits
args=( )
while IFS= read -r -d '' arg; do
args+=( "$arg" )
done </proc/"$pid"/cmdline
(( ${#args[#]} )) || continue # exited while we were running
printf -v cmdline_str '%q ' "${args[#]}"
user=$(stat --format=%U /proc/"$pid") || continue # exited while we were running
printf '%q - %s\n' "$user" "${cmdline_str% }"
done
Unlike the output from ps, which doesn't distinguish between ./command "some argument" and ./command "some" "argument", this will emit output which correctly shows the arguments run by each user, with quoting which will re-run the given command correctly.
What about:
ps -e -o user,comm | egrep "^[^ ]+ +$1$" | cut -d' ' -f1 | sort -u
* Addendum *
This statement:
ps -e -o user,pid,comm | egrep "^\s*\S+\s+\S+\s*$1$" | while read a b; do echo $a; done | sort | uniq -c
or this one:
ps -e -o user,pid,comm | egrep "^\s*\S+\s+\S+\s*sleep$" | xargs -L1 echo | cut -d ' ' -f1 | sort | uniq -c
shows the number of process instances by user.

Bash script checking cpu usage of specific process

First off, I'm new to this. I have some experience with windows scripting and apple script but not much with bash. What I'm trying to do is grab the PID and %CPU of a specific process. then compare the %CPU against a set number, and if it's higher, kill the process. I feel like I'm close, but now I'm getting the following error:
[[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
what am I doing wrong? here's my code so far:
#!/bin/bash
declare -i app_pid
declare -i app_cpu
declare -i cpu_limit
app_name="top"
cpu_limit="50"
app_pid=`ps aux | grep $app_name | grep -v grep | awk {'print $2'}`
app_cpu=`ps aux | grep $app_name | grep -v grep | awk {'print $3'}`
if [[ ! $app_cpu -gt $cpu_limit ]]; then
echo "crap"
else
echo "we're good"
fi
Obviously I'm going to replace the echos in the if/then statement but it's acting as if the statement is true regardless of what the cpu load actually is (I tested this by changing the -gt to -lt and it still echoed "crap"
Thank you for all the help. Oh, and this is on a OS X 10.7 if that is important.
I recommend taking a look at the facilities of ps to avoid multiple horrible things you do.
On my system (ps from procps on linux, GNU awk) I would do this:
ps -C "$app-name" -o pid=,pcpu= |
awk --assign maxcpu="$cpu_limit" '$2>maxcpu {print "crappy pid",$1}'
The problem is that bash can't handle decimals. You can just multiply them by 100 and work with plain integers instead:
#!/bin/bash
declare -i app_pid
declare -i app_cpu
declare -i cpu_limit
app_name="top"
cpu_limit="5000"
app_pid=`ps aux | grep $app_name | grep -v grep | awk {'print $2'}`
app_cpu=`ps aux | grep $app_name | grep -v grep | awk {'print $3*100'}`
if [[ $app_cpu -gt $cpu_limit ]]; then
echo "crap"
else
echo "we're good"
fi
Keep in mind that CPU percentage is a suboptimal measurement of application health. If you have two processes running infinite loops on a single core system, no other application of the same priority will ever go over 33%, even if they're trashing around.
#!/bin/sh
PROCESS="java"
PID=`pgrep $PROCESS | tail -n 1`
CPU=`top -b -p $PID -n 1 | tail -n 1 | awk '{print $9}'`
echo $CPU
I came up with this, using top and bc.
Use it by passing in ex: ./script apache2 50 # max 50%
If there are many PIDs matching your program argument, only one will be calculated, based on how top lists them. I could have extended the script by catching them all and avergaing the percentage or something, but this will have to do.
You can also pass in a number, ./script.sh 12345 50, which will force it to use an exact PID.
#!/bin/bash
# 1: ['command\ name' or PID number(,s)] 2: MAX_CPU_PERCENT
[[ $# -ne 2 ]] && exit 1
PID_NAMES=$1
# get all PIDS as nn,nn,nn
if [[ ! "$PID_NAMES" =~ ^[0-9,]+$ ]] ; then
PIDS=$(pgrep -d ',' -x $PID_NAMES)
else
PIDS=$PID_NAMES
fi
# echo "$PIDS $MAX_CPU"
MAX_CPU="$2"
MAX_CPU="$(echo "($MAX_CPU+0.5)/1" | bc)"
LOOP=1
while [[ $LOOP -eq 1 ]] ; do
sleep 0.3s
# Depending on your 'top' version and OS you might have
# to change head and tail line-numbers
LINE="$(top -b -d 0 -n 1 -p $PIDS | head -n 8 \
| tail -n 1 | sed -r 's/[ ]+/,/g' | \
sed -r 's/^\,|\,$//')"
# If multiple processes in $PIDS, $LINE will only match\
# the most active process
CURR_PID=$(echo "$LINE" | cut -d ',' -f 1)
# calculate cpu limits
CURR_CPU_FLOAT=$(echo "$LINE"| cut -d ',' -f 9)
CURR_CPU=$(echo "($CURR_CPU_FLOAT+0.5)/1" | bc)
echo "PID $CURR_PID: $CURR_CPU""%"
if [[ $CURR_CPU -ge $MAX_CPU ]] ; then
echo "PID $CURR_PID ($PID_NAMES) went over $MAX_CPU""%"
echo "[[ $CURR_CPU""% -ge $MAX_CPU""% ]]"
LOOP=0
break
fi
done
echo "Stopped"
Erik, I used a modified version of your code to create a new script that does something similar. Hope you don't mind it.
A bash script to get the CPU usage by process
usage:
nohup ./check_proc bwengine 70 &
bwegnine is the process name we want to monitor 70 is to log only when the process is using over 70% of the CPU.
Check the logs at: /var/log/check_procs.log
The output should be like:
DATE | TOTAL CPU | CPU USAGE | Process details
Example:
03/12/14 17:11 |20.99|98| ProdPROXY-ProdProxyPA.tra
03/12/14 17:11 |20.99|100| ProdPROXY-ProdProxyPA.tra
Link to the full blog:
http://felipeferreira.net/?p=1453
It is also useful to have app_user information available to test whether the current user has the rights to kill/modify the running process. This information can be obtained along with the needed app_pid and app_cpu by using read eliminating the need for awk or any other 3rd party parser:
read app_user app_pid tmp_cpu stuff <<< \
$( ps aux | grep "$app_name" | grep -v "grep\|defunct\|${0##*/}" )
You can then get your app_cpu * 100 with:
app_cpu=$((${tmp_cpu%.*} * 100))
Note: Including defunct and ${0##*/} in grep -v prevents against multiple processes matching $app_name.
I use top to check some details. It provides a few more details like CPU time.
On Linux this would be:
top -b -n 1 | grep $app_name
On Mac, with its BSD version of top:
top -l 1 | grep $app_name

Resources