LinuxQuestions.org
Help answer threads with 0 replies.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices

Reply
 
Search this Thread
Old 09-05-2011, 08:33 PM   #1
Mouse750
Member
 
Registered: Aug 2011
Distribution: Ubuntu
Posts: 50

Rep: Reputation: Disabled
Bash to ash: Help converting a script?


Ok, the awesome members here at linuxquestions helped (understatement) me modify a pick a random card bash script into this beauty:

Code:
#!/bin/bash
# The goal of this script is randomly 
# pick a vpn server for openwrt routers.

# Put ovpn files in this directory:
cd /etc/config/vpn_servers/

# List Server Names Here
servers=(
	Chicago_01.ovpn	
	Dallas_06.ovpn	
	LA_04.ovpn	
	LasVegas_03.ovpn	
	Phoenix_04.ovpn	
	SaltLakeCity_05.ovpn	
	Virginia_02.ovpn
)

# Count The Number of Servers.
count_the_servers=${#servers[*]}

# Main Part
# Begin Loop
while true; do

	killall openvpn
	sleep 5
		
	#Pick A Random Server
	openvpn --config "${servers[RANDOM%count_the_servers]}" &
				
	#Pick new server in 1 Hour.
	sleep 3595
					
# End of Loop
done
The scripts works perfect, I couldn't have asked for more. You guys are great!!

Now I'd like to pay it forward and help others by sharing the script.

Problem is, bash in not normally installed on the target devices. I was able to expand my routers limited flash memory via usb and install Bash to make the script work but that's not going to work out so well for others.

However, the target device does have ash. (#!/bin/sh)

Can somebody please convert, or help convert this? Or is it not possible?

awk is installed by default, I can list any others if you ask...

Thank You.

Last edited by Mouse750; 09-05-2011 at 10:05 PM.
 
Old 09-06-2011, 05:48 AM   #2
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
First, you don't want to modify the script whenever something minor changes. Create /etc/config/vpn_servers.conf and set the important variables there, e.g.
Code:
# Settings for the OpenVPN rotation.

# Number of seconds to use each configuration.
INTERVAL=3595

# Number of seconds to delay after stopping OpenVPN.
DELAY=5

# Directory where the .ovpn files are located.
CONFIGDIR=/etc/config/vpn_servers/
The script itself is below. It is untested. If the configuration file above does not exist, it uses the values above as defaults. The script requires sh find od bc wc sed mktemp cat to be installed to work. The script scans the CONFIGDIR for .ovpn files each time, so you don't need to restart the script, or really do anything, when adding new .ovpn files. If you create or modify the configuration file above, then you do need to restart the script.
Code:
#!/bin/sh

# Config file setting INTERVAL, DELAY, CONFIGDIR variables
if [ -f /etc/config/vpn_servers.conf ]; then
	. /etc/config/vpn_servers.conf
fi

# Defaults
[ -n "$INTERVAL"  ] || INTERVAL=3595
[ -n "$DELAY"     ] || DELAY=5
[ -n "$CONFIGDIR" ] || CONFIGDIR=/etc/config/vpn_servers/

while [ 1 ]; do

	# Create a temporary file for config files
	TEMPLIST=$(mktemp) || exit 1

	# List all config files into the temporary file
	find "$CONFIGDIR" -maxdepth 1 -name '*.ovpn' '!' -name '*
*' > "$TEMPLIST"

	# Count the config files
	COUNT=$[ $(cat "$TEMPLIST" | wc -l) -0 ]
	[ $COUNT -gt 0 ] || exit 1

	# Generate a large random number
	INDEX=$(od -A n -N 4 -t u4 /dev/urandom) +1 )

	# Limit the random number to the config file range
	INDEX=$(echo '(' $INDEX '%' $COUNT ')' + 1 | bc)

	# Pick a config file at random
	CONFIG=$(sed -n -e "$INDEX p" "$TEMPLIST")

	# Remove temporary file
	rm -f "$TEMPLIST"

	# Check that the config file exists
	[ -f "$CONFIG" ] || continue

	# Restart OpenVPN using the new config file.
	killall openvpn
	sleep $DELAY
	( openvpn --config "$CONFIG" & )
				
	# Pick new server in 1 Hour.
	sleep $INTERVAL
done
If you have /usr/bin/logger installed, you might consider expanding the error handling by logging the reason the script exits to syslog.

The script could be rewritten to use awk instead of mktemp cat od bc . Alternatively, you could use just perl or python depending on which you have installed. It is also simple enough to be rewritten in e.g. plain C, if you need to minimize the dependencies.

Last edited by Nominal Animal; 09-06-2011 at 11:14 AM. Reason: Fixed CONFIGDIR value. It is a directory.
 
1 members found this post helpful.
Old 09-06-2011, 09:44 AM   #3
Mouse750
Member
 
Registered: Aug 2011
Distribution: Ubuntu
Posts: 50

Original Poster
Rep: Reputation: Disabled
Thank You for all the effort...

Not Installed:

od, bc, perl, or python.

Installed by default:

/usr/bin/logger
sh, find, wc, sed, mktemp, cat, and awk

Last edited by Mouse750; 09-06-2011 at 09:45 AM.
 
Old 09-06-2011, 11:13 AM   #4
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
Okay, here is the script rewritten to use awk. Untested.
Code:
#!/bin/sh

# Config file setting INTERVAL, DELAY, CONFIGDIR variables
if [ -f /etc/config/vpn_servers.conf ]; then
	. /etc/config/vpn_servers.conf
fi

# Defaults
[ -n "$INTERVAL"  ] || INTERVAL=3595
[ -n "$DELAY"     ] || DELAY=5
[ -n "$CONFIGDIR" ] || CONFIGDIR=/etc/config/vpn_servers/

while [ 1 ]; do

	# Use find to list all config files, and
	# awk to pick one file at random.
	CONFIG=$(find "$CONFIGDIR" -maxdepth 1 -name '*.ovpn' '!' -name '*
*' | awk '{ file[n++] = $0 } END { srand(); print file[int(n * rand())] }') 

	# Check that the config file exists
	[ -f "$CONFIG" ] || continue

	# Restart OpenVPN using the new config file
	killall openvpn

	# Wait for DELAY seconds before starting openvpn
	sleep $DELAY
	( openvpn --config "$CONFIG" & )
				
	# Use this server for INTERVAL seconds
	sleep $INTERVAL
done
 
1 members found this post helpful.
Old 09-06-2011, 02:46 PM   #5
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Debian sid + kde 3.5 & 4.4
Posts: 6,823

Rep: Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950
Nominal Animal probably has the better solution, as he appears to understand exactly what you want to do. His suggestion of an external config file is good.

But as a scripting exercise, here's my previous script converted into posix-compatible syntax, with a minimal number of alterations. I used the info listed here:

http://mywiki.wooledge.org/Bashism

The biggest challenge is the lack of arrays, but by using the positional parameters instead, it actually requires few other changes:

Code:
#!/bin/sh

# The goal of this script is randomly
# pick a vpn server for openwrt routers.

# Put ovpn files in this directory:
cd /etc/config/vpn_servers/

# List Server Names Here.
set -- "Chicago_01.ovpn" \
       "Dallas_06.ovpn" \
       "LA_04.ovpn" \
       "LasVegas_03.ovpn" \
       "Phoenix_04.ovpn" \
       "SaltLakeCity_05.ovpn" \
       "Virginia_02.ovpn"

# Count The Number of Servers.
count_the_servers="$#"

# Main Part
# Begin Loop
while true; do

     killall openvpn
     sleep 5

     #Pick A Random Server
     random="$( awk 'BEGIN{ srand(); printf "%d", ( rand() * 10000 ) }' )"
     server="$(( random % count_the_servers +1 ))"
     eval server="\$$server"

     openvpn --config "$server" &

     #Pick new server in 1 Hour.
     sleep 3595

#End of Loop
done
But if all the .ovpn files sit inside the /etc/config/vpn_servers/ directory, then you can simply change the line above to...
Code:
set -- *.ovpn
...which will load them into the "array".

If I had realized this before, we could've also done the same in my original script too:
Code:
servers=( *.ovpn )
The rest of the script values can then also be moved to an external file and sourced, instead of hard-coded, as NA demonstrated.
 
1 members found this post helpful.
Old 09-06-2011, 06:55 PM   #6
Mouse750
Member
 
Registered: Aug 2011
Distribution: Ubuntu
Posts: 50

Original Poster
Rep: Reputation: Disabled
I don't know what to say... You guys are too kind! A simple Thank You doesn't quite sum up my feelings.

Both scripts work out of the box, I'm sure that takes some skill. You guys did it blind.

Sorry for the late reply but I have been playing with both scripts to see "how" they work.

In David the H's I did change it to set -- *.ovpn as suggested. It's a nice change because it uncomplicates the script. I don't understand what the rand() * 10000 does. Why the 10000? Also I noticed you used eval and I remember you telling me in the past to try not using it.

And again, Nominal Animal version is very clean looking and looks very professional. Please forgive me but I do not understand the need for the sleep command variables. Also since you built the defaults into it, I didn't see the need to use an external conf?

Would this also be acceptable?:

Code:
#!/bin/sh

# Defaults
[ -n "$CONFIGDIR" ] || CONFIGDIR=/etc/config/vpn_servers/

while true; do

	# Use find to list all config files, and                                                                                                             
	# awk to pick one file at random.                                                                                                                    
	CONFIG=$(find "$CONFIGDIR" -maxdepth 1 -name '*.ovpn' '!' -name '*                                                                                   
	*' | awk '{ file[n++] = $0 } END { srand(); print file[int(n * rand())] }')                                                                          
                                                                                                                         
	# Restart OpenVPN using the new config file                                                                                                          
	killall openvpn                                                                                                                                      

	# Wait 5 seconds before starting openvpn                                                                                                     
	sleep 5                                                                                                                                              
	( openvpn --config "$CONFIG" & )                                                                                                                     

	# Use this server for 7200 seconds                                                                                                                   
	sleep 7200                                                                                                                                           
done
#
Or is it just more professional the way you originally intended? Sorry if it seems like I'm chopping up your hard work. I'm just trying to dissect it, so that I can learn from it.

Last edited by Mouse750; 09-06-2011 at 09:03 PM.
 
Old 09-07-2011, 05:51 PM   #7
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Debian sid + kde 3.5 & 4.4
Posts: 6,823

Rep: Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950Reputation: 1950
awk's rand function outputs numbers as a six-decimal-place floating-point between zero and one (i.e. "0.566305"). Multiplying the result by 10000 shifts it so there are four-digits in front of the decimal, which is then truncated into an integer with the %d (digit) printf option. You could use any multiplier 100 or greater, really.

Yes, I like to stress that eval is something that should generally be avoided whenever possible, and in most cases there are better solutions available. But it's not always bad; you just need to know when and how to use it properly. The main concern is to make sure that it only evaluates lines with known values in it; otherwise you're at risk of executing malicious code.

http://mywiki.wooledge.org/BashFAQ/048

In this case, there's no risk because the script ensures that the variable to be evaluated only contains a nice safe number. But there really isn't much choice anyway, as there's no other reasonable way to indirectly reference the needed parameter in a posix shell.

I think NA's point with the separate config file is that the script itself should hold only the processing code while things that are user-specified should be set as inputs to the script in some fashion, and in principle I agree. It allows you to change the settings as necessary without messing with the script itself. "Defaults" are of course just fallbacks that the script will use if it can't import the configured input values for some reason (e.g. the file becomes corrupted). It's generally a good idea to code for such contingencies.

But if you're really confident the settings will never change, then I say go ahead and simply hard-code them.
 
1 members found this post helpful.
Old 09-08-2011, 02:32 PM   #8
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
Quote:
Originally Posted by David the H. View Post
awk's rand function outputs numbers as a six-decimal-place floating-point between zero and one (i.e. "0.566305"). Multiplying the result by 10000 shifts it so there are four-digits in front of the decimal, which is then truncated into an integer with the %d (digit) printf option. You could use any multiplier 100 or greater, really.
Right. In general, you can use int(rand() * N) to produce a random integer between 0 and N-1 inclusive, up to at least a million. The value will be always less than N. Remember to call srand() first though, to set a new random seed based on the system time, or you will get the same sequence (depending on awk variant and version).

Quote:
Originally Posted by David the H. View Post
I think NA's point with the separate config file is that the script itself should hold only the processing code while things that are user-specified should be set as inputs to the script in some fashion, and in principle I agree.
Exactly. For something as simple as this script it is not necessary, but it is an important principle.

(As an aside, I think two different example solutions are always better than just one. It gives the reader multiple viewpoints to the problem, and may eventually lead to a better solution than either one alone. I think it is a very good thing you showed a different approach, David the H. Thank you.)

I know of several largeish Linux clusters that use a script to maintain firewall settings, with the settings contained in the script itself. Roughly once a year somebody manages to lock up a cluster by mis-editing the script. I did write a replacement script that uses separate config files, and cancels the changes unless the user expressly and interactively confirms the changes, but it was rejected: apparently the rare firewall lock-up is less of a disruption than changing something that 'works'.

Which is kind of my point: Learn the way to do things efficiently in the long term. You never know which script ends up being used for the next decade or so. Separating configuration from the script is one of the ways. It lowers the probability of typos due to changes. For simple variables, sourcing a config file (like in my example above) works well, and you can use
Code:
sh -c '. path/to/config.file >/dev/null 2>/dev/null' || echo "Error in config file!"
to check the syntax in the configuration file. (The above command does it in a specific separate shell, so current shell is irrelevant, and unaffected by sourcing the config file.)

If you have the settings within the script, the only way you can test it is by running it. If you were to expand your script to a full service, you could do the check before reload -- like Apache does. Then, when an administrator modifies the configuration and issues sudo service this-service reload , the configuration is checked first. If there is a problem in the configuration, the service is not taken offline; the administrator only gets a warning that there is a b0rk in the config, so no changes are done. (It still surprises me how most admins still use restart instead of reload -- and then do a headdesk when their buggy changes stop the service from working, and get an angry call from their boss and/or client. As you can see from my examples above, avoiding stuff like that does not require that much more effort.)

There is a secondary reason why I wrote the example script the way I did. I suspect you will eventually need to change the script from being a standalone script running always, to either a script run regularly via cron, or to a full-blown service. The former is trivial; just omit the loop and the sleep $INTERVAL bit. For the latter, I'd personally write a small C program instead, mainly to keep resource use minimal. The logic would follow the script very closely, though.

(The typical reason for moving to cron or a service script is ease of maintenance, for example through a web-based interface. Standalone scripts tend to be a pain to manage automatically -- each one needing their own management stuff --, while cron scripts and services follow very simple rules, and are therefore much easier to manage: you only need one interface to manage all possible cron scripts, and one interface to manage all service scripts, regardless of the script contents.)
 
1 members found this post helpful.
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] Converting bash script into PHP suse_nerd Programming 7 07-11-2011 06:36 AM
[SOLVED] Converting YYYYMMDD to date in Bash script knockoutned Programming 4 08-03-2010 09:07 AM
converting a py script to bash saawan Linux - Software 2 05-06-2009 12:56 PM
converting bash script to csh DJOtaku Programming 8 02-13-2006 06:35 AM
bash script for converting ps to pdf juergenkemeter Linux - General 3 10-10-2005 05:35 PM


All times are GMT -5. The time now is 09:27 AM.

Main Menu
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration