Bash script says: "}" unexpected (expected "done") in function call in .xinitrc - bash

I'm writing the .xinitrc for my system, but the code (below) crashes on line 17 (the ending brace of the `freespace' function) with the error
"}" unexpected (expected "done")
Is there actually a problem with the function declaration, or did I mess up somewhere else in the script?
while true; do
# WiFi connectivity status
if [ -f /sbin/iwgetid ]; then
result="$(/sbin/iwgetid -r )";
if [ -z "$result" ]; then
result="Not connected";
fi
else
result="Not installed";
fi
wifi_status="WiFi: $result";
# Free space on / and /home
function freespace {
local space="$(df -lh --output=avail $1 | tail -n 1 | sed -e 's/^[ \t]*//') free on $1";
echo "$space";
}
root_free=$(freespace /);
home_free=$(freespace /home);
# Miscellaneous
user_str="$(whoami)#$(hostname)";
date_str="$(date +"%a %b %d %R")";
# Battery status and CPU temperature
if [ -f /usr/bin/acpi ]; then
battery_status="Battery: $(acpi -b '{split($0, a, ": "); print a[2]'})";
cpu_temp="CPU temperature: $(acpi -t -f | sed 's/Thermal 0: ok, //g')";
acpi_status="$battery_status | $cpu_temp";
else
acpi_status="ACPI not available";
fi
# System performance
uptime_str=$(uptime -p);
num_cpus="CPUs: $(grep "processor" /proc/cpuinfo | wc -l)";
num_procs="$(ps -e | wc -l) active processes";
load_avgs="Load averages: $(cat /proc/loadavg | awk '{split($0, a, " "); print a[1], a[2], a[3]}')";
# Update top and bottom status bar
top_bar="$wifi_status | $uptime_str | $user_str | $date_str";
bottom_bar="$acpi_status | $num_cpus | $num_procs | $load_avgs | $root_free | $home_free";
xsetroot -name "$top_bar;$bottom_bar";
sleep 30;
done &
xbindkeys
( ( sleep 5 && /usr/bin/xscreensaver -no-splash -display :0.0 ) & )
( ( create-random-gradient && feh --bg-center ~/.wallpaper.png ) & )
( redshifter & )
wmname LG3D
exec /home/ma/build/dwm/dwm
If I slim down the code to just that section, i.e.
while true; do
# Free space on / and /home
function freespace {
local space="$(df -lh --output=avail $1 | tail -n 1 | sed -e 's/^[ \t]*//') free on $1";
echo "$space";
}
root_free=$(freespace /);
home_free=$(freespace /home);
echo "$root_free | $home_free";
sleep 2
done
and run it from the terminal directly (instead of calling startx after logging in), it works just fine.

This error will happen if the shell running this code is not actually bash. It's noteworthy that your script doesn't include a shebang -- without #!/usr/bin/env bash or another shebang, the interpreter used is unspecified. (Explicitly specifying sh yourscript will override the shebang and force sh, just as bash yourscript will ignore the shebang and use bash, but that's neither here nor there).
function is not a keyword in standard POSIX sh -- it's a ksh extension which bash adopted. Consequently, to a POSIX shell, the { in function freespace { is not syntax -- it's an argument to be passed to a command named function.
By contrast, the } is parsed as syntax, but it doesn't match with any syntactic opening brace; hence, your error.
In standards-compliant syntax, the declaration would be:
freespace() {
df -lh --output=avail "$1" | awk -v fs="$1" 'NR == 2 { print $1 " free on " fs }'
}
Note the elimination of the unnecessary command substitution. echo "$(...)" is self-defeating: $() goes to significant overhead to capture stdout of its command into a string, and then echo emits that back to stdout again. Why do any of that when you could just run the command directly?

Related

make the bash script to be faster

I have a fairly large list of websites in "file.txt" and wanted to check if the words "Hello World!" in the site in the list using looping and curl.
i.e in "file.txt" :
blabla.com
blabla2.com
blabla3.com
then my code :
#!/bin/bash
put() {
printf "list : "
read list
run=$(cat $list)
}
put
scan_list() {
for run in $(cat $list);do
if [[ $(curl -skL ${run}) =~ "Hello World!" ]];then
printf "${run} Hello World! \n"
else
printf "${run} No Hello:( \n"
fi
done
}
scan_list
this takes a lot of time, is there a way to make the checking process faster?
Use xargs:
% tr '\12' '\0' < file.txt | \
xargs -0 -r -n 1 -t -P 3 sh -c '
if curl -skL "$1" | grep -q "Hello World!"; then
echo "$1 Hello World!"
exit
fi
echo "$1 No Hello:("
' _
Use tr to convert returns in the file.txt to nulls (\0).
Pass through xargs with -0 option to parse by nulls.
The -r option prevents the command from being ran if the input is empty. This is only available on Linux, so for macOS or *BSD you will need to check that file.txt is not empty before running.
The -n 1 permits only one file per execution.
The -t option is debugging, it prints the command before it is ran.
We allow 3 simultaneous commands in parallel with the -P 3 option.
Using sh -c with a single quoted multi-line command, we substitute $1 for the entries from the file.
The _ fills in the $0 argument, so our entries are $1.

Numbered options for bash completion

Can we get numbered completion options with bash when we have started typing part of the option? For example with ksh I am able to get a numbered list of options and then when I press 3 followed by 3 I get the 3rd option completed.
> cat local.
1) local.cshrc
2) local.login
3) local.profile
> cat local.profile
Is this kind of completion possible using bash programmable completion?
With something like this I am able to give numbers for all available options, but this breaks when the user starts to type the option and presses tab so a fallback to normal options without numbers has to be used.
(( COMP_CWORD > 2 )) && return #This stops completion at third word for z oe
if [[ "${COMP_WORDS[2]}" = "" || $( echo "${COMP_WORDS[2]}" | perl -ne 'use Scalar::Util qw(looks_like_number); if (looks_like_number($_)){print 1}else{print 0}' ) -eq 1 ]]; then
#Show all instances oratab + running
local suggestions=($( compgen -W "$( { z ot | grep -v ^$ | grep -v SIDs | grep -v : && z p1; } | perl -lpe 's/\s+$//' | sort | uniq | perl -ne 'print "$.) $_"' )" -- "${COMP_WORDS[2]}" ))
if [ "${#suggestions[#]}" == "1" ]; then #One match found
local cmd=$( echo "${suggestions[0]}" | perl -pe 's/[1-9]+\)[\s]+//' )
COMPREPLY=("$cmd ") #Space needed here to handle -o nospace
return
else
#More than one suggestions resolved, respond with the suggestions intact
COMPREPLY=("${suggestions[#]}")
return
fi
else
local suggestions=( $( compgen -W "$( { z ot | grep -v ^$ | grep -v SIDs | grep -v : && z p1; } | perl -lpe 's/\s+$//' | sort | uniq )" -- "${COMP_WORDS[2]}" ) )
if [ "${#suggestions[#]}" == "1" ]; then #One match found
local cmd="${suggestions[0]}"
COMPREPLY=("$cmd ") #Space needed here to handle -o nospace
return
else
#More than one suggestions resolved, respond with the suggestions intact
COMPREPLY=("${suggestions[#]}")
return
fi
fi
Is there way to do the ksh like completion number + tab with bash completion?

Basic bash script issue

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

Integer Expression Expected

Code sample :
declare -i a=1
echo "The number of NMON instances running in Performance VM"
ps -ef | grep nmon | awk '{ print $2 }' | wc -l
echo "---------------------------------------------"
num2= ps -ef | grep nmon | awk '{ print $2 }' | wc -l
#num1=${num2%%.*}
#num2 = $(ps -ef | grep nmon | awk '{ print $2 }' | wc -l)
echo "---------------------------------------------"
echo "${num2}"
while [ "$a" -lt "$num2" ]
do
kill -USR2 $(ps -ef | grep nmon | awk '{ print $2 }' | head -1)
a=`expr $a + 1`
done
In the Output i am getting the following error
[: : integer expression expected
in the debug it shows
++ '[' 1 -lt '' ']'
that num2 is empty but when i echo the num2 value i am getting the value correctly.
Output:
The number of NMON instances running in Performance VM
1
1
thanks in advance
The 1 you see in the output is not from echo "${num2}". Like the diagnostics already tell you, this variable is empty.
The general syntax of shell scripts is
[ variable=value ...] command parameters ...
which will assign value to variable for the duration of command, then restore its original value. So the pipeline you are running temporarily sets num2 to the empty string (which apparently it already contained anyway), then runs the pipeline without storing the output anywhere (such as, I imagine you expected, in num2).
Here is a fixed version of your script, with the additional change that the Awk scripts handle stuff you used grep and head and wc for. Because the functionality of these commands is easily replaced within Awk, using external utilities is doubtful (especially so for grep which really is useless when you just run it as a preprocessor for a simple Awk script).
countnmon () {
ps -ef | awk '/[n]mon/ { ++n } END { print n }'
}
declare -i a=1
echo "The number of NMON instances running in Performance VM"
countnmon
echo "---------------------------------------------"
num2=$(countnmon)
#num1=${num2%%.*}
#num2 = $(countnmon)
echo "---------------------------------------------"
echo "${num2}"
while [ "$a" -lt "$num2" ]
do
kill -USR2 $(ps -ef | awk '/[n]mon/ { print $2; exit }')
a=`expr $a + 1`
done
The repeated code could be refactored even further to avoid all code duplication but that will somewhat hamper the readability of this simple script so I have not done that.
Whitespaces matter in bash.
The syntax for command execution is:
command arg1 arg2 ...
So,
var = value # command: var, arg1: =, arg2: value
There's are two exceptions to this rule
exporting variables to an executed command (the variables vanish after the command finishes):
var1=value1 var2=value2 .. command arg1 arg2 ...
assigning a variable (you want this one):
var=value

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.

Resources