I need to do some integer math in csh (and no, other shells are not an option, nor is bc, nor is perl, nor is python, period).
In bash my task would look like
seq 1 1 10 > m.txt #supplied from elsewhere
a=2 #supplied from elsewhere
b=3 #supplied from elsewhere
head -n $[$a*$b] m.txt # the line in question
then the question is Is there an expression in csh that computes $[$a*$b] inline?
I know that I can do # c = $a * $b in csh, but that's not inline. I did a little bit of googling and searching SO, but no success so far, so any help is greatly appreciated!
Are your use of square-brackets meant to indicate an array notation or matrix math? csh has no such built-in features.
ELSE, if you mean like bash $(($a * $b)), you can use csh cmd-substitution with backquotes to give you
head -n `expr $a \* $b` m.txt
Note that if your goal was to avoid spawning extra processes, this does not meet your goal, but it is "in-line"
Edit I see I mistyped as $( $a * $b ), see inline correction above.
IHTH.
Without using something outside of the shell, no.
The usual culprit for math from old school shell scripts is expr:
head -n `expr $a \* $b` m.txt
but if that's just as verboten as bc et al, then you're out of luck. Period.
Yes, but it's not pretty:
% seq 1 1 10 > m.txt
% set a = 2
% set b = 3
% head -n `# tmp = $a * $b ; echo $tmp ; unset tmp` m.txt
1
2
3
4
5
6
Note that this will clobber $tmp if you happen to have a variable of that name, so choose a unique name.
(Though I wonder why bc, perl, and python are not an option.)
Related
This is my very first shell script for a Unix class, this is one of the scripts I hope to submit for my final. However there are a few kinks I cannot seem to clear up, it seems to be arithmetic operation errors, and I can't seem to figure it out. Please be kind! thank you so much for your time.
lightgreen=`echo -en "\e[92m"
echo What are the values of a, b \& c?
LIGHTRED=`echo -en "\e[101m"
echo a value:
read a
echo b value:
read b
echo c value:
read c
discrim=$(($b**2 - 4*$a*$c))
sqrtd=$((sqrt($discrim) | bc ))
echo test $sqrtd
echo ${lightgreen}The discriminant is:${discrim}
#xone=$((( -$b + sqrt$discrim) / (2 * $a) | bc ))
#xtwo=$((( -$b - sqrt$discrim) / (2 * $a) | bc ))
xone=$((echo (-1*$b + sqrt($discrim)) / (2*$a) | bc ))
xtwo=$((echo (-1*$b - sqrt($discrim)) / (2*$a) | bc ))
echo ${lightgreen}The discriminant is:${discrim}
#if [$discrim -lt 0 ]
# echo $LIGHTRED There are no real solutions.
#
#
#
echo The two solutions are $xone $xtwo
I have tried to mess with the syntax a good amount, I'm not sure if it's the parentheses that mess me up or the sqrt function, I have tried to incorporate | bc but to no avail. Any help is greatly appreciated! :)
Don't hesitate to call man bash, man bc manual pages.
Use https://www.shellcheck.net/ to check your shell scripts.
Shellcheck also exists on command line and in Visual Studio Code with extension.
#! /usr/bin/env bash
# The first line is very important to now the name of the interpreter
# Always close " , ' , or ` sequences with same character
# Do not use old `...` syntax, replaced by $(...)
# Here, use $'...', to assign value with \... sequences
lightgreen=$'\e[92m'
lightred=$'\e[101m'
normal=$'\e[0m'
# It's better to put phrase between "..." or '...'
echo "What are the values of a, b & c?"
# Use read -p option to specify prompt
# Use read -r option to not act backslash as an escape character
read -p "a value: " -r a
read -p "b value: " -r b
read -p "c value: " -r c
# With bash only, it's only possible to use integer values
# discrim=$(($b**2 - 4*$a*$c))
# use bc instead
discrim=$(bc -l <<<"$b^2 - 4*$a*$c")
# The syntax:
# bc <<<"..."
# is equivalent to:
# echo "..." | bc
# but without pipe (|)
# Close the color change with normal return
echo "${lightgreen}The discriminant is: ${discrim}${normal}"
if [[ "${discrim:0:1}" == "-" ]]; then
echo "${lightred}There are no real solutions${normal}"
# ... complex ...
else
sqrtd=$(bc -l <<<"sqrt($discrim)")
echo "sqrt($discrim)=$sqrtd"
xone=$(bc -l <<<"(-1*$b + $sqrtd) / (2*$a)")
xtwo=$(bc -l <<<"(-1*$b - $sqrtd) / (2*$a)")
echo "The two solutions are: $xone and $xtwo"
fi
This question already has answers here:
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Brace expansion with a Bash variable - {0..$foo}
(5 answers)
Closed 8 years ago.
I'm making a program in bash that creates a histoplot, using numbers I have created. The numbers are stored as such (where the 1st number is how many words are on a line of a file, and the 2nd number is how many times this amount of words on a line comes up, in a given file.)
1 1
2 4
3 1
4 2
this should produce:
1 #
2 ####
3 #
4 ##
BUT the output I'm getting is:
1 #
2 #
3 #
4 #
however the for loop is not recognising that my variable "hashNo" is a number.
#!/bin/bash
if [ -e $f ] ; then
while read line
do
lineAmnt=${line% *}
hashNo=${line##* }
#VVVV Problem is this line here VVVV
for i in {1..$hashNo}
#This line ^^^^^^^ the {1..$hashNo}
do
hashes+="#"
done
printf "%4s" $lineAmnt
printf " $hashes\n"
hashes=""
done < $1
fi
the code works if I replace hashNo with a number (eg 4 makes 4 hashes in my output) but it needs to be able to change with each line (no all lines on a file will have the same amount of chars in them.
thanks for any help :D
A sequence expression in bash must be formed from either integers or characters, no parameter substitutions take place before hand. That's because, as per the bash doco:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
In other words, brace expansion (which includes the sequence expression form) happens first.
In any case, this cries out to be done as a function so that it can be done easily from anywhere, and also made more efficient:
#!/bin/bash
hashes() {
sz=$1
while [[ $sz -ge 10 ]]; do
printf "##########"
((sz -= 10))
done
while [[ $sz -gt 0 ]]; do
printf "#"
((sz--))
done
}
echo 1 "$(hashes 1)"
echo 2 "$(hashes 4)"
echo 3 "$(hashes 1)"
echo 4 "$(hashes 2)"
which outputs, as desired:
1 #
2 ####
3 #
4 ##
The use of the first loop (doing ten hashes at a time) will almost certainly be more efficient than adding one character at a time and you can, if you wish, do a size-50 loop before that for even more efficiencies if your values can be larger.
I tried this for (( i=1; i<=$hashNo; i++ )) for the for loop, it seems to be working
Your loop should be
for ((i=0; i<hashNo; i++))
do
hashes+="#"
done
Also you can stick with your loop by the use of eval and command substitution $()
for i in $(eval echo {1..$hashNo})
do
hashes+="#"
done
When I run commands in my shell as below, it returns an expr: non-integer argument error. Can someone please explain this to me?
$ x=20
$ y=5
$ expr x / y
expr: non-integer argument
Those variables are shell variables. To expand them as parameters to another program (ie expr), you need to use the $ prefix:
expr $x / $y
The reason it complained is because it thought you were trying to operate on alphabetic characters (ie non-integer)
If you are using the Bash shell, you can achieve the same result using expression syntax:
echo $((x / y))
Or:
z=$((x / y))
echo $z
I believe it was already mentioned in other threads:
calc(){ awk "BEGIN { print "$*" }"; }
then you can simply type :
calc 7.5/3.2
2.34375
In your case it will be:
x=20; y=3;
calc $x/$y
or if you prefer, add this as a separate script and make it available in $PATH so you will always have it in your local shell:
#!/bin/bash
calc(){ awk "BEGIN { print $* }"; }
Why not use let; I find it much easier.
Here's an example you may find useful:
start=`date +%s`
# ... do something that takes a while ...
sleep 71
end=`date +%s`
let deltatime=end-start
let hours=deltatime/3600
let minutes=(deltatime/60)%60
let seconds=deltatime%60
printf "Time spent: %d:%02d:%02d\n" $hours $minutes $seconds
Another simple example - calculate number of days since 1970:
let days=$(date +%s)/86400
Referencing Bash Variables Requires Parameter Expansion
The default shell on most Linux distributions is Bash. In Bash, variables must use a dollar sign prefix for parameter expansion. For example:
x=20
y=5
expr $x / $y
Of course, Bash also has arithmetic operators and a special arithmetic expansion syntax, so there's no need to invoke the expr binary as a separate process. You can let the shell do all the work like this:
x=20; y=5
echo $((x / y))
To get the numbers after decimal point, you can do this:-
read num1 num2
div=`echo $num1 / $num2 | bc -l`
echo $div
let's suppose
x=50
y=5
then
z=$((x/y))
this will work properly .
But if you want to use / operator in case statements than it can't resolve it.
In that case use simple strings like div or devide or something else.
See the code
I had to do a division in shell script and the best way was:
result1=`echo "scale=3; ($var1 / $total) * 100"| bc -l`
result2=`echo "scale=3; ($var2 / $total) * 100"| bc -l`
but I want to compare the values of $result1 and $result2
Using if test $result1 -lt $result2 or if [ $result1 -gt $result2 ] didn't work :(
Any idea how to do that?
You can compare floating-point numbers using expr(1):
: nr#yorkie 3724 ; expr 3.1 '<' 3.3
1
: nr#yorkie 3725 ; expr 3.1 '<' 3.09
0
You can also have bc do the comparisons as well as the calculations:
if [ "$(echo $result1 '<' $result2 | bc -l)" -eq 1 ];then ... fi
Finally, ksh93 can do arithmetic evaluation $(($result1 < $result2)) with floating-point numbers, although bash cannot.
note that you've gotta be a bit careful when dealing with floating point numbers and if you are testing for equality you really want to decide on some precision and then compare using that. Something like:
if (abs(x1-x2) < 0.0001) then equal # pseudo-code
the reason being that with computers we're dealing with limited-precision binary fractions not true mathematical reals. Limiting the precision in bc with the scale=3 will have this effect.
I'd also advise against trying to do this stuff in shell script. It's not that you can't do it but you'll have to fork off lots of little sub commands to do the tricky bits and that's slow to execute and generally a pain to write - you spend most of your time trying to get the shell to do what you want rather than writing the code you really want. Drop into a more sophisticated scripting language instead; my language of choice is perl but there are others. like this...
echo $var1 $var2 $total | perl -ne 'my ($var1, $var2, $tot) = split /\s+/; if ($var1/$tot == $var2/$tot) { print "equal\n"; }'
also note that you're dividing by the same value ($total in your question) so the whole comparison can be done against the numerators (var1 and var2) provided $total is positive
Posting a new answer since I cannot yet comment...
#Norman Ramsey's answer is not quite accurate:
expr will perform an integer or string comparison, not a floating-point comparison.
Here's what the man page says:
expr1 {=, >, >=, <, <=, !=} expr2
Return the results of integer comparison if both arguments are integers; otherwise, returns the results of string comparison using the locale-specific collation sequence.
(just try expr 8.9 '<' 10 and get 0 where it should be 1).
bcworks great, but isn't always installed.
So another alternative is using perl -e:
perl -e 'print expression' will print 1 if expression is true and nothing (empty string) otherwise.
e.g. perl -e 'print 8.9 < 10' - prints "1", while perl -e 'print 2>4' prints nothing.
And when used in if statement:
if [ $(perl -e "print $result1 < $result2") ];then ... fi
I have created a little password generation script. I'm curious to what improvements can be made for it except input error handling, usage information etc. It's the core functionality I'm interested in seeing improvements upon.
This is what it does (and what I like it to do):
Keep it easy to change which Lowercase characters (L), Uppercase characters (U), Numbers (N) and Symbols (S) that are used in passwords.
I'd like it to find a new password of legnth 10 for me in max two seconds.
It should take a variable length of the password string as an argument.
Only a password containing at least one L, U, N and S should be accepted.
Here is the code:
#!/bin/bash
PASSWORDLENGTH=$1
RNDSOURCE=/dev/urandom
L="acdefghjkmnpqrtuvwxy"
U="ABDEFGHJLQRTY"
N="012345679"
S="\-/\\)?=+.%#"
until [ $(echo $password | grep [$L] | grep [$U] | grep [$N] | grep -c [$S] ) == 1 ]; do
password=$(cat $RNDSOURCE | tr -cd "$L$U$N$S" | head -c $PASSWORDLENGTH)
echo In progress: $password # It's simply for debug purposes, ignore it
done
echo Final password: $password
My questions are:
Is there a nicer way of checking if the password is acceptable than the way I'm doing it?
What about the actual password generation?
Any coding style improvements? (The short variable names are temporary. Though I'm using uppercase names for "constants" [I know there formally are none] and lowercase for variables. Do you like it?)
Let's vote on the most improved version. :-)
For me it was just an exercise mostly for fun and as a learning experience, albeit I will start using it instead of the generation from KeepassX which I'm using now. It will be interesting to see which improvements and suggestions will come from more experienced Bashistas (I made that word up).
I created a little basic script to measure performance: (In case someone thinks it's fun)
#!/bin/bash
SAMPLES=100
SCALE=3
echo -e "PL\tMax\tMin\tAvg"
for p in $(seq 4 50); do
bcstr=""; max=-98765; min=98765
for s in $(seq 1 $SAMPLES); do
gt=$(\time -f %e ./genpassw.sh $p 2>&1 1>/dev/null)
bcstr="$gt + $bcstr"
max=$(echo "if($max < $gt ) $gt else $max" | bc)
min=$(echo "if($min > $gt ) $gt else $min" | bc)
done
bcstr="scale=$SCALE;($bcstr 0)/$SAMPLES"
avg=$(echo $bcstr | bc)
echo -e "$p\t$max\t$min\t$avg"
done
You're throwing away a bunch of randomness in your input stream. Keep those bytes around and translate them into your character set. Replace the password=... statement in your loop with the following:
ALL="$L$U$N$S"
password=$(tr "\000-\377" "$ALL$ALL$ALL$ALL$ALL" < $RNDSOURCE | head -c $PASSWORDLENGTH)
The repetition of $ALL is to ensure that there are >=255 characters in the "map to" set.
I also removed the gratuitous use of cat.
(Edited to clarify that what appears above is not intended to replace the full script, just the inner loop.)
Edit: Here's a much faster strategy that doesn't call out to external programs:
#!/bin/bash
PASSWORDLENGTH=$1
RNDSOURCE=/dev/urandom
L="acdefghjkmnpqrtuvwxy"
U="ABDEFGHJLQRTY"
N="012345679"
# (Use this with tr.)
#S='\-/\\)?=+.%#'
# (Use this for bash.)
S='-/\)?=+.%#'
ALL="$L$U$N$S"
# This function echoes a random index into it's argument.
function rndindex() { echo $(($RANDOM % ${#1})); }
# Make sure the password contains at least one of each class.
password="${L:$(rndindex $L):1}${U:$(rndindex $U):1}${N:$(rndindex $N):1}${S:$(rndindex $S):1}"
# Add random other characters to the password until it is the desired length.
while [[ ${#password} -lt $PASSWORDLENGTH ]]
do
password=$password${ALL:$(rndindex $ALL):1}
done
# Now shuffle it.
chars=$password
password=""
while [[ ${#password} -lt $PASSWORDLENGTH ]]
do
n=$(rndindex $chars)
ch=${chars:$n:1}
password="$password$ch"
if [[ $n == $(( ${#chars} - 1 )) ]]; then
chars="${chars:0:$n}"
elif [[ $n == 0 ]]; then
chars="${chars:1}"
else
chars="${chars:0:$n}${chars:$((n+1))}"
fi
done
echo $password
Timing tests show this runs 5-20x faster than the original script, and the time is more predictable from one run to the next.
you could just use uuidgen or pwgen to generate your random passwords, maybe later shuffling some letters around or something of the sort
secpwgen is very good (it can also generate easier to remember diceware passwords) - but has almost disappeared from the net. I managed to track down a copy of the 1.3 source & put it on github.
It is also now part of Alpine Linux.