You are not logged in.
Hi everyone, not sure if this could warrant a stickied topic, or maybe just a thread that can gain some traction. I'd been given the advice that a good way to learn and become a bit more adept at scripting is to look at other people's scripts, read them, focus on understanding the 'why' and the logic behind why they are written as they are. So, I decided why not start right here at home with some BunsenLab scripts. (I hope the original writers don't take umbrage!)
I'm going to start with an arbitrary choice of scripts, the bunsenlabs tint2 restart script. Here it is below for reference:
#!/bin/bash
#
# bl-tint2restart to be used by bl-tint2-pipemenu
# Written by damo <damo@bunsenlabs.org> for BunsenLabs Linux, April 2015
# and johnraff <john@bunsenlabs.org> July 2015
declare -A commands # associative array
while read pid cmd; do
if [[ ${cmd%% *} = tint2 ]]; then
kill "$pid"
commands[$cmd]=1 # duplicate commands will be launched only once
fi
done <<< "$(pgrep -a tint2)"
sleep 1
# any processes still running will be killed with SIGKILL
while read pid cmd; do
if [[ ${cmd%% *} = tint2 ]]; then
kill -KILL "$pid"
commands[$cmd]=1
fi
done <<< "$(pgrep -a tint2)"
sleep 1
for i in "${!commands[@]}" # go through the indexes
do
(setsid $i &)
sleep 0.1
done
sleep 1
bl-compositor --restart # restart compositor
exit 0
So breaking this down, I can see the logic of creating an array to store the unknown amount of output that we'll get from the if test. Because multiple tint2 processes can be running, the while loop could return many lines.
1. Now my first query comes from the choice of using the ${cmd%% *} = tint2
wouldn't just using grep to find tint2 be easier and simplier?
2. A big question comes up with the use of the line:
commands[$cmd]=1
This give the array that we made an association of $cmd. Which expands to the stored info we received during the while read loops. Then it sets those outputs equal to the value of 1. Why is this, what does it really mean that the tint2 process(s) have a value of 1 now?
The $cmd having values of 1 doesn't seem to come into play in the rest of the script?
3. The question I have is the use of the setsid command. From what I gathered, this creates the process to run in a new session. Why is this needed? If we killed the tint2 commands in the loops, can't we just start tint2 with just running it with systemctl or simply by using the tint2 command?
Thanks those who respond!! Also let me know if this is viable thread to keep open...Perhaps we (us noobies) can post script/code fragments, and have them looked at, examined, and the logic discussed...
"I have not failed, I have found 10,000 ways that will not work" -Edison
Offline
.....
1. Now my first query comes from the choice of using the ${cmd%% *} = tint2
wouldn't just using grep to find tint2 be easier and simplier?
....
Look at the output of grep (pgrep in this case), which still has to be parsed:
[damo@debian ~]$ pgrep -a tint2
18690 tint2 -c /home/damo/.config/tint2/tint2rc-damo
This would have to be piped to eg awk, to extract the second field, which is another process. Parameter substitution is generally more efficient, and relatively simple. The `pid` and `cmd` variables have already been assigned as well.
Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt «» BunsenLabs on DeviantArt
Offline
This would have to be piped to eg awk, to extract the second field, which is another process. Parameter substitution is generally more efficient, and relatively simple. The `pid` and `cmd` variables have already been assigned as well.
Thanks damo, as the author or this one, when you write scripts do you consciously go out of your way to make them as lean and efficient as possible? Or do you just sort of go with what you know?
Also could you explain abit about question 2, with the array set to 1?
"I have not failed, I have found 10,000 ways that will not work" -Edison
Offline
Well, the way it works is that I have an idea for a script to improve the user experience(hopefully!), so I throw together a bunch of greps and awks and poorly-defined vars. Then along come @johnraff, @nobody and @xaos52 and they re-write them properly!
So, yes I "go with what I know", and learn from the feedback.
In the case of the bl tint2 scripts, there was quite a lot of to-and-fro to make the script more "elegant", and to deal with possible scenarios.
Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt «» BunsenLabs on DeviantArt
Offline
So this is doing something else than "killall tint2 && tint2" ?
Offline
So this is doing something else than "killall tint2 && tint2" ?
Yes - the script needs to deal with multiple instances of tint, and restarts the compositor if appropriate.
Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt «» BunsenLabs on DeviantArt
Offline
pkill -SIGUSR1 tint2 && pkill -SIGUSR1 compton
"-SIGUSR1" reloads the configuration files for tint2, compton and conky as well
Obviously, "-SIGHUP" would be the traditional signal for this but the first user option seems to work with most programs.
Offline
Could one of you explain a bit for the line
commands[$cmd]=1
What's the deal with setting the value of $cmd (the processes of tint2) equal to the value of 1?
"I have not failed, I have found 10,000 ways that will not work" -Edison
Offline
commands is an associative array. Unlike indexed arrays which have a numbered list of strings, this one has a list of strings associated with other strings not integers.
So
commands[$cmd]=1
is setting the array element whose index is $cmd to 1.
In fact, in this case we could set it to anything - the actual number is meaningless. We are collecting the tint2 commands in the indexes of the array rather than in the values (which would seem more logical) because an array can only have one element with a certain index. (Logical if you think about it.) This gives us a simple way of eliminating duplicates, without having to call external commands like uniq.
Also here: https://forums.bunsenlabs.org/viewtopic … 202#p11202
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
About setsid: if tint2 is launched from the script as a normal fork '$cmd &' then when the script reaches the end it will have one or more tint2 children running, so cannot exit till they have all finished. The remaining script process will hang around to complicate things like pgrep searches - and is just useless anyway.
exec would work if there was only one tint2 to launch, but with a list the first one would take over and the rest would be ignored.
setsid is very powerful forking. I've launched a web radio with setsid and it carried on playing after I had logged out! After shutting down the computer...
Used in this somewhat gnarly script: https://forums.bunsenlabs.org/viewtopic.php?id=434
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
..... We are collecting the tint2 commands in the indexes of the array rather than in the values (which would seem more logical) because an array can only have one element with a certain index
That's what I thought was happening, but I couldn't quite get the logic sorted out enough in my head to express it!
Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt «» BunsenLabs on DeviantArt
Offline
Johnraff : Thank you!! That makes so much sense... Jeez I admit, I'd make a horrible...horrible programmer. This type of finesse, level of forethought and planning would be completely lost on me.
So as for the associative array: I used the example that you linked to. But I modified it and made it into this:
declare -A list
for i in 'first' 'second' 'third' 'fourth' 'second'
do
list[$i]=coffee
done
for x in "${!list[@]}"
do
echo "$x"
done
So when I run this, I only get the output in the array of "first" "second" "third" "fourth"
So it doesn't output the 2nd listing of the "second" variable. This is because each variable is assigned to the value of coffee. And the value of coffee ( I picked the first word that came to mind) can only have a 1 to 1 relationship with a variable. So in the tint2 restart, script... It assigns all of tint2 processes to the value of 1. So there will not be two identical processes with the same value of 1. The duplicate processes won't be doubly launched with the setsid command...
phew...
Thanks again Raff, and please correct me if I goof'd on that..
"I have not failed, I have found 10,000 ways that will not work" -Edison
Offline
Remember, "${!list[@]}" lists all the indexes of the array. To get the values you'd use "${list[@]}" and if you substituted that, your code snippet would output "coffee" "coffee" "coffee" "coffee" .
Two (or more) array elements can have the same value, but only one element can have a certain index. So, as I said above, the value "1" is quite meaningless - it could be x, . or even null.
For example:
john@raffles4:~$ declare -A t
john@raffles4:~$ t[one]=
john@raffles4:~$ t[two]=2
john@raffles4:~$ echo "${!t[@]}"
one two
john@raffles4:~$ echo "${t[@]}"
2
The array indexes are just used to store a list of commands. It's a handy way to eliminate duplicate strings, as long as you don't care what order they come out.
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
Remember, "${!list[@]}" lists all the indexes of the array. To get the values you'd use "${list[@]}" and if you substituted that, your code snippet would output "coffee" "coffee" "coffee" "coffee" .
Two (or more) array elements can have the same value, but only one element can have a certain index. So, as I said above, the value "1" is quite meaningless - it could be x, . or even null.
For example:john@raffles4:~$ declare -A t john@raffles4:~$ t[one]= john@raffles4:~$ t[two]=2 john@raffles4:~$ echo "${!t[@]}" one two john@raffles4:~$ echo "${t[@]}" 2
The array indexes are just used to store a list of commands. It's a handy way to eliminate duplicate strings, as long as you don't care what order they come out.
Yep, I got it! You are a gentleman and a scholar my friend. The thing for myself is learning and remember when to employ something like this.
"I have not failed, I have found 10,000 ways that will not work" -Edison
Offline
So I wanted to go through another script from our B.L library o' configs.
This time I'll be using the bl-user-add script, which is pretty straight forward. The script /usr/lib/bunsen/bunsen-configs/bl-usr-setup is used by the lightdm process on boot. This is shown below in the
/etc/lightdm/lightdm.bunsen.conf file.
session-setup-script=/usr/lib/bunsen/bunsen-configs/bl-user-setup
And now the main User Set-up script...
#!/bin/bash
# BunsenLabs User Set-up
# USER="$1" USER is exported by lightdm
[[ "$USER" = root || ! -d /home/"$USER" ]] && { echo "$0: variable USER has not been set correctly." >&2; exit 1;}
[ -f "/home/$USER/.config/bunsen/bl-setup" ] && exit 0
bkp_sfx="~$( date +%FT%T )~"
rsync -rltb --suffix="$bkp_sfx" --safe-links /usr/share/bunsen/skel/ /home/$USER
for i in "/home/$USER/.gtk-bookmarks" "/home/$USER/.config/nitrogen/nitrogen.cfg"
do
[ -f "${i}.template" ] || continue
sed --in-place "s/%USERNAME%/$USER/g" "${i}.template"
if [ -f "$i" ]
then
if diff -BEbZ "$i" "${i}.template" >/dev/null
then
rm "${i}.template"
else
mv "$i" "${i}${bkp_sfx}"
mv "${i}.template" "$i"
fi
else
mv "${i}.template" "$i"
fi
done
ln -sTf /usr/share/backgrounds "/home/$USER/Pictures/wallpapers/shared"
mkdir -p "/home/$USER/.config/bunsen" # this should already exist
touch "/home/$USER/.config/bunsen/bl-setup"
chown -R "$USER":"$USER" "/home/$USER"
exit
So stepping through this I'll bring up some points, and questions, and if anyone is game, feel free (please please please!) post to ask questions or answers to questions.
1. # USER="$1" USER is exported by lightdm So the first comment i have is this commented line. Is this supposed to read "USER is *imported* by lightdm? Because the next line is already using the USER variable without it being set in this script. So I'm assuming lightdm brings along the USER variable from another script?
2. [[ "$USER" = root || ! -d /home/"$USER" ]] && { echo "$0: variable USER has not been set correctly." >&2; exit 1;} If the user is root, or if the user doesn't have a home directory (not even sure how that's possible) then, exit with the statement and error out.
3. [ -f "/home/$USER/.config/bunsen/bl-setup" ] && exit 0 Testing if the bl-setup "placeholder" file is already present. Basically what this is checking for is if the user already exists, and if this script has been run already. It does this by way of place a dummy "canary" bl-setup file in the user's home dir. If it is indeed present the script is satisfied and exits successfully.
4. rsync -rltb --suffix="$bkp_sfx" --safe-links /usr/share/bunsen/skel/ /home/$USER
So if we've gotten to this part, it means that the placeholder wasn't there, and that the bl-setup script needs to be run. This command basically moves the default user config to user's home dir. It does so recursively, and respecting all sym links, and time stamps. It also appends the previously set date variable to the end.
5. [ -f "${i}.template" ] || continue Something that ALWAYS throws me off, where I have to stare at the syntax harder, is the || . Basically the previous for loop is checking the newly ported user's dir. for any .template files remaining. It checks the .gtk-bookmarks/ dir and the nitrogen.cfg file. So if it does find anything ending in .template, DON"T continue with the for loop. At this point, does it pick up when the for loop is done? Starting at ln -sTf? Or does it just exit the script?
6. On the assumption that it does find the .templates in place, it continues with the script. The sed command strips out the generic USERNAME placeholder and replaces it with the actual username.
7. So as we move into the weeds of the script I get a bit lost..
if [ -f "$i" ]
then
if diff -BEbZ "$i" "${i}.template" >/dev/null
then
rm "${i}.template"
Above we have an if test opened. If the files that we looped through, exist then perform a diff command on each, comparing it with the .template file and all output going to /dev/null.
So I"m assuming that this is to check if the username has really been applied to the ${i}.template s?
If the diff command succeeds (There is a difference between the two) then go ahead and remove the .template version, as it's no longer needed.
If there is NO difference, it means that the script will manually mv the .template version to the $i file.
8. The last few lines create a sym link from our home user's /Pictures/wallpapers/shared dir. to the /usr/share/backgrounds dir.
It then makes the previously referenced "canary" file in /home/$USER/.config/bunsen/bl-setup (explained earlier)
and last but not least change ownership!
Phew... Hope this reads correctly! If anyone has anything else to add feel free!
The point of these posts (I hope) is to sort of pick apart the some nuiances in bash scripting, and hoping to have other newbs such as myself look at how and why things work under the hood of not only B.L but bash in general!
"I have not failed, I have found 10,000 ways that will not work" -Edison
Offline
1. # USER="$1" USER is exported by lightdm So the first comment i have is this commented line. Is this supposed to read "USER is *imported* by lightdm? Because the next line is already using the USER variable without it being set in this script. So I'm assuming lightdm brings along the USER variable from another script?
I read this as "$1" is the first argument passed to the script from lightdm, which should be the USER identity (using `session-setup-script= ` in `lightdm.conf`)
2. [[ "$USER" = root || ! -d /home/"$USER" ]] && { echo "$0: variable USER has not been set correctly." >&2; exit 1;} If the user is root, or if the user doesn't have a home directory (not even sure how that's possible) then, exit with the statement and error out.
$USER may have deleted their $HOME, or otherwise been fiddling where they shouldn't. Maybe it is in another partition which has failed to mount, or fstab etc is faulty.
5. [ -f "${i}.template" ] || continue Something that ALWAYS throws me off, where I have to stare at the syntax harder, is the || . Basically the previous for loop is checking the newly ported user's dir. for any .template files remaining. It checks the .gtk-bookmarks/ dir and the nitrogen.cfg file. So if it does find anything ending in .template, DON"T continue with the for loop. At this point, does it pick up when the for loop is done? Starting at ln -sTf? Or does it just exit the script?
See your bash documentation...
"continue causes a jump to the next iteration of the loop, skipping all the remaining commands in that particular loop cycle."
and for "&&" and "||":
"The right side of && will only be evaluated if the exit status of the left side is zero. || is the opposite: it will evaluate the right side only if the left side exit status is nonzero."
Like in the consruction of commands you might do
sudo apt-get update && sudo apt-get upgrade
ie if update has run successfully, then run upgrade.
I see the "||" construction as a shorthand version of "if blah is not true then do something" - in this case we skip straight to the next cycle of the loop if we can't find that .template file.
Last edited by damo (2016-03-11 03:00:36)
Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt «» BunsenLabs on DeviantArt
Offline
As the writer, I guess it would look bad if I wasn't able to explain that script...
@HB you managed to lose the indentation when you pasted in that code - it makes loops and if clauses easier to read. Here's that loop again:
for i in "/home/$USER/.gtk-bookmarks" "/home/$USER/.config/nitrogen/nitrogen.cfg"
do
[ -f "${i}.template" ] || continue
sed --in-place "s/%USERNAME%/$USER/g" "${i}.template"
if [ -f "$i" ]
then
if diff -BEbZ "$i" "${i}.template" >/dev/null
then
rm "${i}.template"
else
mv "$i" "${i}${bkp_sfx}"
mv "${i}.template" "$i"
fi
else
mv "${i}.template" "$i"
fi
done
1. # USER="$1" USER is exported by lightdm So the first comment i have is this commented line. Is this supposed to read "USER is *imported* by lightdm?
No. LightDM exports the variable so its children - in this case this script - can pick it up. When the script is started by LightDM USER is already available. Slim used to pass the user name to the CrunchBang setup script as an argument, and the commented-out line was left there as a reminder that it was no longer necessary.
4. rsync -rltb --suffix="$bkp_sfx" --safe-links /usr/share/bunsen/skel/ /home/$USER
So if we've gotten to this part, it means that the placeholder wasn't there, and that the bl-setup script needs to be run. This command basically moves the default user config to user's home dir. It does so recursively, and respecting all sym links, and time stamps. It also appends the previously set date variable to the end.
The suffix is only for backed-up files. Backups are made if rsync replaces a file with one that has a newer datestamp. (Not the same as the date suffix btw.)
5. [ -f "${i}.template" ] || continue Something that ALWAYS throws me off, where I have to stare at the syntax harder, is the || . Basically the previous for loop is checking the newly ported user's dir. for any .template files remaining. It checks the .gtk-bookmarks/ dir and the nitrogen.cfg file. So if it does find anything ending in .template, DON"T continue with the for loop. At this point, does it pick up when the for loop is done? Starting at ln -sTf? Or does it just exit the script?
Damo has already explained the "continue" command in loops. Have another look at the indented code and you might be able to figure out the rest.
if [ -f "$i" ]
then
if diff -BEbZ "$i" "${i}.template" >/dev/null
then
rm "${i}.template"Above we have an if test opened. If the files that we looped through, exist then perform a diff command on each, comparing it with the .template file and all output going to /dev/null.
So I"m assuming that this is to check if the username has really been applied to the ${i}.template s?
No, it's to check if the new file (after putting in the username) is different from what the user had already.
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
Thanks for your replies guys, as always. Hope you didn't feel obligated to reply. I mean this as a thread where anyone can post and ask questions, and not to be an interrogation.
I see what you mean by the variable USER being taken from Lightdm proper.
In regards to || and &&.. What determines if something evaluates as a 0 (true/successful) or a 1 (false/unsucessful) command is what confused me at first. What I mean is.. if you run an ls command on a directory that is empty, when I was looking for it to be filled, technically is a sucessfully run command, but it's hard to think about some commands being run as successful, when they do the opposite of what you want/expect.
I gotta think more abstractly and logically about the actual command's completion success
"I have not failed, I have found 10,000 ways that will not work" -Edison
Offline
^ As I've said before, I like your questions because they force me to assess my own understanding
Regarding the exit status of commands, and what you are expecting it might be when running them, don't forget......
man <command>
man ls
...
Exit status:
0 if OK,
1 if minor problems (e.g., cannot access subdirectory),
2 if serious trouble (e.g., cannot access command-line argument).
...
So if the dir is empty then `ls` says so - actually it says nothing, but it doesn't return an error, because it exited with "0", ie success. You need to read the manpages for a command to know what it might return in a script.
Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt «» BunsenLabs on DeviantArt
Offline
...
Not really. While 0 conventionally means some sort of successful termination, depending on the program, any non-zero value and even 0 can have different meanings. Read the man pages for more information.
Isn't that what I said? `ls` exits with "0" if success.
Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt «» BunsenLabs on DeviantArt
Offline