Bash Script Counting Instances of Itself Wrongly

Bash script counting instances of itself wrongly

Finally found a way, albeit an ugly one, partially inspired from the question @TomFenech linked in his answer:

#!/bin/bash
nb=$(ps f | grep '[c]ount_itself.sh' | grep -v ' \\_')
echo "$nb"
sleep 20

Execution :

root@myserver:/# ./count_itself.sh
17725 pts/1 S+ 0:00 \_ /bin/bash ./count_itself.sh

Execution with one already running in bg :

root@myserver:/# ./count_itself.sh &
[1] 17733
root@myserver:/# ./count_itself.sh
17733 pts/1 S 0:00 \_ /bin/bash ./count_itself.sh
17739 pts/1 S+ 0:00 \_ /bin/bash ./count_itself.sh

Explanation (from what I've understood) :

  • ps f returns the tree of active processes
  • grep '[c]ount_itself.sh' restricts the previous command to only showing instances of count_itself.sh

Returns

17808 pts/1    S+     0:00  \_ /bin/bash ./count_itself.sh
17809 pts/1 S+ 0:00 \_ /bin/bash ./count_itself.sh
  • grep -v ' \\_' excludes rows containing 4 spaces (equivalent of a tab) then \_, which correspond to subprocesses

bash: differing newline count when assigning result to variable

  • Command substitution itself runs in a subshell so thats one bash process

  • your search for bash ($0) i.e. grep -c bash also ends up in the process table at that time so thats another process (grep) containing string bash. Note that, this might not show up in the process table at the time of running, depending on how busy your system is.

  • And you have two (or whatever) actual bash processes (sessions) running presumably are the rest

You can use a Regex trick to get rid of the false positive i.e. grep one from count:

ps ax | grep -c "[b]ash"

It would still count the subshell while doing command substitution:

var=$(ps ax | grep -c "[b]ash")

So you need to manually remove one from this count.

Example:

$ var=$(ps ax | grep -c "bash")    
$ echo $var
4

$ var=$(ps ax | grep -c "[b]ash")
$ echo $var
3

Bash script that kills other instances of itself if they're running

Here's the basis

kill_others() {
local mypid=$$ # capture this run's pid

declare pids=($(pgrep -f ${0##*/} # get all the pids running this script

for pid in ${pids[@]/$mypid/}; do # cycle through all pids except this one
kill $pid # kill the other pids
sleep 1 # give time to complete
done
}

declare -i count=0
while [[ $(pgrep -f ${0##*/}|wc -l) -gt 1 ]]; do
kill_outhers
((++count))
if [[ $count -gt 10 ]]; then
echo "ERROR: can't kill pids" >&2
exit 1
fi
done

Counter increment in Bash loop not working

First, you are not increasing the counter. Changing COUNTER=$((COUNTER)) into COUNTER=$((COUNTER + 1)) or COUNTER=$[COUNTER + 1] will increase it.

Second, it's trickier to back-propagate subshell variables to the callee as you surmise. Variables in a subshell are not available outside the subshell. These are variables local to the child process.

One way to solve it is using a temp file for storing the intermediate value:

TEMPFILE=/tmp/$$.tmp
echo 0 > $TEMPFILE

# Loop goes here
# Fetch the value and increase it
COUNTER=$[$(cat $TEMPFILE) + 1]

# Store the new value
echo $COUNTER > $TEMPFILE

# Loop done, script done, delete the file
unlink $TEMPFILE

Correctly count number of lines a bash variable

The wc counts the number of newline chars. You can use grep -c '^' for counting lines.
You can see the difference with:

#!/bin/bash

count_it() {
echo "Variablie contains $2: ==>$1<=="
echo -n 'grep:'; echo -n "$1" | grep -c '^'
echo -n 'wc :'; echo -n "$1" | wc -l
echo
}

VAR=''
count_it "$VAR" "empty variable"

VAR='one line'
count_it "$VAR" "one line without \n at the end"

VAR='line1
'
count_it "$VAR" "one line with \n at the end"

VAR='line1
line2'
count_it "$VAR" "two lines without \n at the end"

VAR='line1
line2
'
count_it "$VAR" "two lines with \n at the end"

what produces:

Variablie contains empty variable: ==><==
grep:0
wc : 0

Variablie contains one line without \n at the end: ==>one line<==
grep:1
wc : 0

Variablie contains one line with \n at the end: ==>line1
<==
grep:1
wc : 1

Variablie contains two lines without \n at the end: ==>line1
line2<==
grep:2
wc : 1

Variablie contains two lines with \n at the end: ==>line1
line2
<==
grep:2
wc : 2

Count occurrences of a char in a string using Bash

I would use the following awk command:

string="text,text,text,text"
char=","
awk -F"${char}" '{print NF-1}' <<< "${string}"

I'm splitting the string by $char and print the number of resulting fields minus 1.

If your shell does not support the <<< operator, use echo:

echo "${string}" | awk -F"${char}" '{print NF-1}'

Reliable way for a Bash script to get the full path to itself

Here's what I've come up with (edit: plus some tweaks provided by sfstewman, levigroker, Kyle Strand, and Rob Kennedy), that seems to mostly fit my "better" criteria:

SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"

That SCRIPTPATH line seems particularly roundabout, but we need it rather than SCRIPTPATH=`pwd` in order to properly handle spaces and symlinks.

The inclusion of output redirection (>/dev/null 2>&1) handles the rare(?) case where cd might produce output that would interfere with the surrounding $( ... ) capture. (Such as cd being overridden to also ls a directory after switching to it.)

Note also that esoteric situations, such as executing a script that isn't coming from a file in an accessible file system at all (which is perfectly possible), is not catered to there (or in any of the other answers I've seen).

The -- after cd and before "$0" are in case the directory starts with a -.



Related Topics



Leave a reply



Submit