I'm trying to compare values of two variables both containing strings-as-numbers. For example:
var1="5.4.7.1"
var2="6.2.4.5"
var3="1-4"
var4="1-5"
var5="2.3-3"
var6="2.3.4"
Sadly, I don't even know where to start... Any help will be appreciated!
What I meant is how would I go about comparing the value of $var5 to $var6 and determine with one of them is higher.
EDIT: Better description of the problem.
You can use [[ ${str1} < ${str2} ]] style test. This should work:
function max()
{
[[ "$1" < "$2" ]] && echo $2 || echo $1
}
max=$(max ${var5} ${var6})
echo "max=${max}."
It depends of the required portability of the solution. If you don't care about that and you use a deb based distribution, you can use the dpkg --compare-versions feature.
However, if you need to run your script on distros without dpkg I would use following approach.
The value you need to compare consist of first (the first element) and the rest (all others). The first is usually called the head and the rest - tail, but I deliberately use names first and rest, to not confuse with head(1) and tail(1) tools available on Unix systems.
In case first($var1) is not equal to first($var2) you just compares those firsts elements. If firsts are equal, just recursively run the compare function on rest($var1) and rest($var2). As a border case you need to decide what to do if values are like:
var1 = "2.3.4"
var2 = "2.3"
and in some step you will compare empty and non-empty first.
Hint for implementing first and rest functions:
foo="2.3-4.5"
echo ${foo%%[^0-9][0-9]*}
echo ${foo#[0-9]*[^0-9]}
If those are unclear to you, read man bash section titled Parameter Expansion. Searching the manual for ## string will show you the exact section immediately.
Also, make sure, you are comparing elements numerically not in lexical order. For example compare the result of following commands:
[[ 9 > 10 ]]; echo $?
[[ 9 -gt 10 ]]; echo $?
Related
I want to generate a random number from given list
For example if I give the numbers
1,22,33,400,400,23,12,53 etc.
I want to select a random number from the given numbers.
Couldn't find an exact duplicate of this. So here goes my attempt, exactly what 123 mentions in comments. The solution is portable across shell variants and does not make use of any shell binaries to simplify performance.
You can run the below commands directly on the console.
# Read the elements into bash array, with IFS being the de-limiter for input
IFS="," read -ra randomNos <<< "1,22,33,400,400,23,12,53"
# Print the random numbers using the '$RANDOM' variable built-in modulo with
# array length.
printf "%s\n" "${randomNos[ $RANDOM % ${#randomNos[#]}]}"
As per the comments below, if you want to ignore a certain list of numbers from a range to select; do the approach as below
#!/bin/bash
# Initilzing the ignore list with the numbers you have mentioned
declare -A ignoreList='([21]="1" [25]="1" [53]="1" [80]="1" [143]="1" [587]="1" [990]="1" [993]="1")'
# Generating the random number
randomNumber="$(($RANDOM % 1023))"
# Printing the number if it is not in the ignore list
[[ ! -n "${ignoreList["$randomNumber"]}" ]] && printf "%s\n" "$randomNumber"
You can save it in a bash variable like
randomPortNumber=$([[ ! -n "${ignoreList["$randomNumber"]}" ]] && printf "%s\n" "$randomNumber")
Remember associative-arrays need bash version ≥4 to work.
I am currently running a program that rearranges genomes to create the best alignment to a reference genome, and as it does so it generates a number of folders like alignment#.
I have no way of knowing how many iterations this program will run through before it stops, but the final alignment is the best one (could be anything from alignment5 to alignment35) and will have a predictable filename within the folder, though the folder will be changeable.
I need a bash script that will look inside a directory and identify the highest-numbered directory and store it as a variable or similar, that could ideally be passed to an additional program.
I just wanted to add that my scripting is very basic. If you guys could explain your answers as thoroughly as possible or provide links to user-friendly resources that would be much appreciated.
A concept script here:
#!/bin/bash
shopt -s extglob || exit
DIR="/parent/dir" highest=
for a in "$DIR"/alignment+([[:digit:]]); do
b=${a##*/alignment}
[[ -z $highest || b -gt highest ]] && highest=$b
done
[[ -n $highest ]] && echo "Highest: $DIR/alignment${highest}"
ls -1 $directory | sort --numeric
This assumes a consistent prefix to the file names.
Otherwise, you can use "sort -k N --numeric", see "man sort" for details.
I'm having hard time figuring how to do this:
In my HW I need to compare between 2 strings that might contain numbers too,
Here are some examples:
A>a
ad>abc
Abc>U2
Is there any way to do it when i have both strings in variables ?!.
I believe you want something like:
if [[ "$string1" < "$string2" ]];
then
comparison='<' ;
else
if [[ "$string1" < "$string2" ]];
then
comparison='>'
else
comparison='='
fi
fi
echo "${string1}${comparison}${string2}"
But please note that it highly depends on your LOCALE !
I'd recommand to add, at first line of the script, a change of locale to ensure you use the proper one. For example:
LC_ALL='C'
(see also : LC_COLLATE, LC_LANG, etc. if you only need a specific locale for specific tests. But changing LC_ALL changes them all)
You can know the supported (installed) locales with:
locale -a
and the currently used locale with:
locale
To compare strings, the usual way is [[ $var1 < $var2 ]] .
I am developing a "wrapper script" to use as a "logging aid" in Bash.
It should print out information about the call stack at the time it was invoked.
I have done work on it, which follows, but a couple of questions/doubts remain and I'd like to get the best possible answer on them from the experts here.
My code:
################################################################################
# Formats a logging message.
function my_function_format_logging_message() {
local -r TIMESTAMP="$(date '+%H:%M:%S')"
local -r PROCESS="$$" # Deliberately not using $BASHPID, focus: parent process
local -r CALLER="${FUNCNAME[1]}"
local -i call_stack_position=1
if [[ "${CALLER}" == 'my_function_log_trace' ||
"${CALLER}" == 'my_function_log_debug' ||
"${CALLER}" == 'my_function_log_info' ||
"${CALLER}" == 'my_function_log_warning' ||
"${CALLER}" == 'my_function_log_error' ||
"${CALLER}" == 'my_function_log_critical' ]]
then
call_stack_position=$((call_stack_position++))
fi
local -r SOURCE="$(basename "${BASH_SOURCE[$call_stack_position]}")"
local -r FUNCTION="${FUNCNAME[$call_stack_position]}"
local -r LINE="${BASH_LINENO[$call_stack_position-1]}" # Previous function
local -r SEVERITY="$1"
local -r MESSAGE="$2"
# TODO: perform argument validation
printf '%s [PID %s] %s %s %s:%s - %s\n' \
"${TIMESTAMP}" \
"${PROCESS}" \
"${SEVERITY}" \
"${SOURCE}" \
"${FUNCTION}" \
"${LINE}" \
"${MESSAGE}"
}
################################################################################
Usage example:
my_function_format_logging_message CRITICAL Temporarily increasing energy level to 9001
or:
my_function_log_info Dropping back to power level 42
My doubts:
call_stack_position=$((call_stack_position++))
I can't think of a better way to increment this variable, is there a nicer/more readable form of this?
Can I use a better construct to detect if the call was made by a logging method? (e.g. trace, debug, info..). All of those if statements make my eyes hurt.
Am I reinventing the wheel / misusing the tool I'd like to learn? (i.e. shell scripting)
I might be reinventing the wheel, sure, but this is self-training.. to one day stop being a toll booth night-shift worker.
NOTE
I am looking for a match to the specified my_function_log_* names and no others. It is not ok to assume I have that degree of freedom (the many ifs are there for exactly that reason and I am looking for some syntactic sugar or better use of language features to do that type of "set membership" test).
I can suggest this for your first two questions:
if [[ "${CALLER}" == my_function_log_* ]]
then
let call_stack_position++
fi
If you just want a set of values after log_:
if [[ "${CALLER}" =~ my_function_log_(trace|debug|info|warning|error|critical) ]]
then
let call_stack_position++
fi
Bash’s type system, if you even want to call it that, is very rudimentary: strings and integers are its only first class citizens, arrays are a tacked on afterthought whose functionality is nowhere near that of Python sets or Ruby arrays. This being said, there is a poor man’s in operator for arrays that relies on string matching. Given an array of function names:
log_functions=(my_function_log_trace my_function_log_debug my_function_log_info my_function_log_warning my_function_log_error my_function_log_critical)
this:
[[ ${log_functions[*]} =~ \\b$CALLER\\b ]]
will match only members of the array. And as we are talking poor man’s constructs, you can combine the above pattern with boolean control operators into a poor man’s ternary assignment to skip the numerical evaluation altogether:
local -i call_stack_position=$([[ ${log_functions[*]} =~ \\b$CALLER\\b ]] && echo 1 || echo 2)
Caveat: on systems that do not support the GNU extensions to regcomp() (notably OS X and Cygwin), word boundary matching needs to use the somewhat more verbose character class form, i.e.
[[ ${log_functions[*]} =~ [[:\<:]]$CALLER[[:\>:]] ]]
Notes: seeing your code and noting you mentioned you are learning shell scripting, I’d offer two observations unrelated to the question proper:
The brace notation for variable expansion is only required for array access, expansion operations and to disambiguate var names in string concatenation. It is not needed in other cases, i.e. in both your tests and your printf command.
Using expansion string operations is much faster than using externals and thus recommended wherever possible. Instead of using basename, use ${var##*/}.
A more readable way of doing an increment is by incrementing it in numerical context:
(( call_stack_position++ ))
For the matching, you can use a glob in bash:
[[ $CALLER == my_function_log_* ]]
As far as reinventing the wheel, you can use syslog logging from bash using the logger command. The local syslog daemon will handle formatting the log message and writing it to a file.
logger -p local0.info "CRITICAL Temporarily increasing energy level to 9001"
Update, based on comments. You can use an associative array to be more explicit about what you are looking for. It requires bash v4 or higher.
declare -A arr=(
['my_function_log_trace']=1
['my_function_log_debug']=1
['my_function_log_info']=1
['my_function_log_warning']=1
['my_function_log_error']=1
['my_function_log_critical']=1
);
if [[ ${arr[CALLER]} ]]; then
...
fi
You could also use extended globbing for the pattern matching, similar to the regex in perreal's answer, but without regex:
shopt -s extglob
if [[ $CALLER == my_function_log_#(trace|debug|info|warning|error|critical) ]]; then
...
fi
I am doing bash script to check spelling on number of files.
I came across to problem of telling aspell to ignore some set of words that I allow to appear.
This is same as "Ignore All" in interactive mode. But that will not work as I would need to do that by hand.
How can I tell aspell to ignore given set of words. Is there any parameter that can do that.
I wish there was an option to pass file with those words.
Or might be out there a more efficient way for scripting spell checking in bash?
Easy: put your words in a Personal Dictionary: ~/.aspell.en.pws where the first line is
personal_ws-1.1 en 500
(500 is the number of words, doesn't need to be exact, aspell will fix it if you add words with aspell).
If you need the dictionary to be elsewhere, use options like these:
aspell --home-dir=/dir/to/dict --personal=dict-file.txt
This is written in a combination of shell and pseudocode. It does not seem like the most efficient way; parsing two arrays and checking the results takes up more memory and cycles than necessary.
function SpellCheckInit() {
for i in `seq 0 $(( ${#aname[#]} - 1 ))`; do
echo Parsing...
done
}
Dictionary=( "${dictionary[#]}" "Oxford English Dictionary" )
char=( "${words[#]}" "Text" )
echo Ignore?
read -a omt_wrds
SpellCheckInit()
words_left=${#Dictionary[#]}
until [ $words_left -lt 0]; do
if [ "$char" = "i"]; do
echo IGNORED
elif [ "$char" = "$Dictionary"]; do
echo CORRECT
else
for word in Dictionary
$word > $dictionary
done
fi
done