Currency converter - bash

I'm stuck on this USD to SEK currency converter and the empty row, row 12. The current conversion is 1 SEK = 0.158193 USD 1 USD = 6.32138 SEK.
Row 12 will have something like SEK="(?(USD) )?"
I don't know what to enter in the question marks.
#!/bin/bash
shopt -s -o nounset
declare -i USD # USD
declare -i SEK # SEK
# Title
printf "%s\n" "USD-SEK Currency Convertor"
printf "\n"
# Get the value to convert
read -p "Enter a USD: " USD
# Do the conversion
printf "You will get SEK %d\n" "$SEK"
exit 0

You can do floating point arithmetic using bc like this:
SEK=$( echo " 6.32138 * $USD " | bc -l )
Explanation:
Bash does not have built it support for floating point arithmetic. Thus, we usually handle these operation using the bc program. bc reads an arithmetic expression as a string from the standard input, and prints the result the standard output. Note that the -l option is necessary for keeping the decimal part of the expression.
In order to get the result from bc, and store it in a variable, we use command redirection i.e. the $( ). Note that there are no spaces before and after the = in the previous expression.
Complete Example
#!/bin/bash
printf "%s\n" "USD-SEK Currency Convertor"
# Get the value to convert
read -p "Enter a USD: " USD
SEK=$(echo " 6.32138 * $USD " | bc -l )
printf "You will get SEK %s\n" "$SEK" ;# NOTE THAT I CHANGED THIS TO %s FROM %f DUE TO THE LOCALE SETTINGS
Output
$ ./converter.sh
USD-SEK Currency Convertor
Enter a USD: 10
You will get SEK 63.213800
Note that i removed the declare -i SEK from the script, since the SEK variable is NOT Integer
The harm of declare -i. This code produces:
#!/bin/bash
declare -i SEK ;# WOOOPS I FORGOT THE declare -i
printf "%s\n" "USD-SEK Currency Convertor"
# Get the value to convert
read -p "Enter a USD: " USD
SEK=$(echo " 6.32138 * $USD " | bc -l )
printf "You will get SEK %s\n" "$SEK"
This output:
$ ./converter.sh
USD-SEK Currency Convertor
Enter a USD: 10
./converter.sh: line 6: 63.21380: syntax error: invalid arithmetic operator (error token is ".21380")
You will get SEK 0.000000

Related

Tabulate part of text file written by shell

I have a shell script that is writing(echoing) the output on an array to a file. The file is in the following format
The tansaction detials for today are 35
Please check the 5 biggest transactions below
-----------------------------------------------------------------------------------
Client Name,Account Number,Amount,Tran Time
Michael Press,20484,602117,11.41.02
Adam West,164121,50152,11.41.06
John Smith,15113,411700,11.41.07
Leo Anderson,2115116,350056,11.41.07
Wayne Clark,451987,296503,11.41.08
And i have multiple such line.
How do i tabulate the names after ---?
I tried using spaces while echoing the array elements. Also tried tabs. I tried using column -t -s options. But the text above the --- is interfering with the desired output.
The desired output is
The tansaction detials for today are 35
Please check the 5 biggest transactions below
-----------------------------------------------------------------------------------
Client Name Account Number Amount Tran Time
Michael Press 20484 602117 11.41.02
Adam West 164121 50152 11.41.06
John Smith 15113 411700 11.41.07
Leo Anderson 2115116 350056 11.41.07
Wayne Clark 451987 296503 11.41.08
The printing to a file is a part of a bigger script. So, i am looking for a simple solution to plug into this script.
Here's the snippet from that script where i am echoing to the file.
echo "The tansaction detials for today are 35 " >> log.txt
echo "" >> log.txt
echo " Please check the 5 biggest transactios below " >> log.txt
echo "" >> log.txt
echo "-----------------------------------------------------------------------------------" >> log.txt
echo "" >> log.txt
echo "" >> log.txt
echo "Client Name,Account Number,Amount,Tran Time" >> log.txt
array=( `output from a different script` )
x=1
for i in ${array[#]}
do
#echo "Array $x - $i"
Clientname=$(echo $i | cut -f1 -d',')
accountno=$(echo $i | cut -f2 -d',')
amount=$(echo $i | cut -f3 -d',')
trantime=$(echo $i | cut -f4 -d',')
echo "$Clientname,$accountno,$amount,$trantime" >> log.txt
(( x=$x+1 ))
done
I'm not sure to understand everythings =P
but to answer this question :
How do i tabulate the names after ---?
echo -e "Example1\tExample2"
-e means : enable interpretation of backslash escapes
So for your output, I suggest :
echo -e "$Clientname\t$accountno\t$amount\t$trantime" >> log.txt
Edit : If you need more space, you can double,triple,... it
echo -e "Example1\t\tExample2"
If I understand your question, in order to produce the output format of:
Client Name Account Number Amount Tran Time
Michael Press 20484 602117 11.41.02
Adam West 164121 50152 11.41.06
John Smith 15113 411700 11.41.07
Leo Anderson 2115116 350056 11.41.07
Wayne Clark 451987 296503 11.41.08
You should use the output formatting provided by printf instead of echo. For example, for the headings, you can use:
printf "Client Name Account Number Amount Tran Time\n" >> log.txt
instead of:
echo "Client Name,Account Number,Amount,Tran Time" >> log.txt
For writing the five largest amounts and details, you could use:
printf "%-14s%-17s%8s%s\n" "$Clientname" "$accountno" "$amount" "$trantime" >> log.txt
instead of:
echo "$Clientname,$accountno,$amount,$trantime" >> log.txt
If that isn't what you are needing, just drop a comment and let me know and I'm happy to help further.
(you may have to tweak the field widths a bit, I just did a rough count)
True Tabular Output Requires Measuring Each Field
If you want to insure that your data is always in tabular form, you need to measure each field width (including the heading) and then take the max of either the field width (or heading) to set the field width for your output. Below is an example of how that can be done (using your simulated other program input):
#!/bin/bash
ofn="log.txt" # set output filename
# declare variables as array and integer types
declare -a line_arr hdg name acct amt trn tmp
declare -i nmx=0 acmx=0 ammx=0 tmx=0
# set heading array (so you can measure lengths)
hdg=( "Client Name"
"Account Number"
"Ammount"
"Tran Time" )
## set the initial max based on headings
nmx="${#hdg[0]}" # max name width
acmx="${#hdg[1]}" # max account width
ammx="${#hdg[2]}" # max ammount width
tmx="${#hdg[3]}" # max tran width
{ IFS=$'\n' # your array=( `output from a different script` )
line_arr=($(
cat << EOF
Michael Press,20484,602117,11.41.02
Adam West,164121,50152,11.41.06
John Smith,15113,411700,11.41.07
Leo Anderson,2115116,350056,11.41.07
Wayne Clark,451987,296503,11.41.08
EOF
)
)
}
# write heading to file
cat << EOF > "$ofn"
The tansaction detials for today are 35
Please check the 5 biggest transactions below
-----------------------------------------------------------------------------------
EOF
# read line array into tmp, compare to max field widths
{ IFS=$','
for i in "${line_arr[#]}"; do
tmp=( $(printf "%s" "$i") )
((${#tmp[0]} > nmx )) && nmx=${#tmp[0]}
((${#tmp[1]} > acmx )) && acmx=${#tmp[1]}
((${#tmp[2]} > ammx )) && ammx=${#tmp[2]}
((${#tmp[3]} > tmx )) && tmx=${#tmp[3]}
name+=( "${tmp[0]}" ) # fill name array
acct+=( "${tmp[1]}" ) # fill account num array
amt+=( "${tmp[2]}" ) # fill amount array
trn+=( "${tmp[3]}" ) # fill tran array
done
}
printf "%-*s %-*s %-*s %s\n" "$nmx" "${hdg[0]}" "$acmx" "${hdg[1]}" \
"$ammx" "${hdg[2]}" "${hdg[3]}" >> "$ofn"
for ((i = 0; i < ${#name[#]}; i++)); do
printf "%-*s %-*s %-*s %s\n" "$nmx" "${name[i]}" "$acmx" "${acct[i]}" \
"$ammx" "${amt[i]}" "${trn[i]}" >> "$ofn"
done
(you can remove the extra space between each field in the final two printf statements if you only want a single space between them -- looked better with 2 to me)
Output to log.txt
$ cat log.txt
The tansaction detials for today are 35
Please check the 5 biggest transactions below
-----------------------------------------------------------------------------------
Client Name Account Number Ammount Tran Time
Michael Press 20484 602117 11.41.02
Adam West 164121 50152 11.41.06
John Smith 15113 411700 11.41.07
Leo Anderson 2115116 350056 11.41.07
Wayne Clark 451987 296503 11.41.08
Look things over and let me know if you have any questions.

Shell Script output formatting

I have output from a shell script like below
output1 ..... 1
output2 ..... 2
output3 ............3
I tried to format it with equal spacing inside script but output still not have uniform spacing.
I want to print the output like below.
output1 ..... 1
output2 ..... 2
output3 ......3
Are there any commnads available to get this done. I use bash.
here is the code.
lnode=abc
printf "server name ......... "$lnode""
printf "\nserver uptime and load details : ......... `uptime`"
printf "\n"
lcpu=`cat /proc/cpuinfo | grep -i process |wc -l`
printf "Total number of CPUs on this server : ......... $lcpu\n"
-Thanks.
The idea of printf is that you specify a format string that specifies column widths, etc:
$ cat script.sh
lnode=abc
printf "%-40s %s\n" "server name :" "......... $lnode"
printf "%-40s %s\n" "server uptime and load details :" "......... `uptime`"
lcpu=$(cat /proc/cpuinfo | grep -i process |wc -l)
printf "%-40s %s\n" "Total number of CPUs on this server :" "......... $lcpu"
The first directive in the format string, %-40s, is applied to the first argument that follows the format string. It tells printf to display that argument in a 40-character-wide column. If we had used %40s, it would be a right-aligned column. I specified %-40s so that it would be left-aligned.
This produces output like:
$ bash script.sh
server name : ......... abc
server uptime and load details : ......... 18:05:50 up 17 days, 20 users, load average: 0.05, 0.20, 0.33
Total number of CPUs on this server : ......... 4
Documentation
Bash's printf command is similar to printf in other languages, particularly the C version. Details specific to bash are found in man bash. Detailed information about the available format options is found in man 3 printf. To begin, however, you are probably better served by a tutorial such as this one or this one or this one.

printf bash - print text in the middle of existing line surrounded by marks

It doesn't seem that simple. At least for me.
I need to have a variable in printf text. It's something like:
FOO="User data"
+++++++++++++++++++ $FOO +++++++++++++++++++++
Would output
+++++++++++++++++++ User Data +++++++++++++++++++++
But
FOO="Fooooooo barrrr"
+++++++++++++++++++ $FOO +++++++++++++++++++++
Should output
++++++++++++++++ Fooooooo barrrr ++++++++++++++++++
And
FOO="Foooooooooooooooooooo barrrrr"
+++++++++++++++++++ $FOO +++++++++++++++++++++
Should be
+++++++++ Foooooooooooooooooooo barrrrr +++++++++++
As you can see I need a variable to be in the middle of n-length line, surrounded by + mark. How to achieve that using printf and other default-available commands?
(Debian 8)
declare -i x1 x2 x3 width
foo="User data"
width=50 # total width
x2=${#foo}+2 # length of $foo and 2 whitespaces
x1=(50-x2)/2 # length of first part
x3=$width-x1-x2 # length of last part
for ((i=1;i<=$x1;i++)); do echo -n "+"; done
echo -n " $foo "
for ((i=1;i<=$x3;i++)); do echo -n "+"; done
Output:
+++++++++++++++++++ User data ++++++++++++++++++++
With foo="stackoverflow.com":
+++++++++++++++ stackoverflow.com ++++++++++++++++
#!/usr/bin/env bash
linelen=100
char="+"
text=$1
len=$(echo -n $text | wc -m)
fillerlen=$((($linelen - $len - 2) / 2))
filler=$(printf "$char%.0s" $(seq 1 $fillerlen))
echo $filler $text $filler
In the format string for printf, you can specify the "precision" of a string with %${p}s, where $p is the precision. You can take advantage of that by printing nothing (expanding to a space) the desired number of times and then translating the spaces into "+":
$ p=10
$ printf "%${p}s\n" | tr ' ' +
++++++++++
This function takes the length of your line and the string you want to put in its centre, then prints it padded with plus signs:
pad () {
len=$1
string=$2
# ${#string} expands to the length of $string
n_pad=$(( (len - ${#string} - 2) / 2 ))
printf "%${n_pad}s" | tr ' ' +
printf ' %s ' "$string"
printf "%${n_pad}s\n" | tr ' ' +
}
Works like this:
$ pad 50 Test
++++++++++++++++++++++ Test ++++++++++++++++++++++
$ pad 50 "A longer string to be padded"
++++++++++ A longer string to be padded ++++++++++
Notice how you have to quote strings consisting of more than one word, or only the first one will be used.
If the length of your line is not divisible by 2, the padding will be rounded down, but will always be symmetrical.
Try this :
#!/bin/bash
n=50; # You can change the value of n as you please.
var="fooo baar";
size=${#var}
n=$(( n - size ))
n=$(( n / 2 ))
s=$(printf "%-${n}s" "*")
echo "${s// /*} "$var" ${s// /*}" #white-spaces included here.

Creating loop within Unix Script

I'm curious of how I might go about creating a more detailed output within the same scripting file. I would like to modify the script so that after it finds the matching line for the currency code, it outputs the following:
Currency code: {currency code value}
Currency name: {currency name value}
Units per USD: {units value}
USD per unit : {USD value}
What I have so far:
#!/bin/bash
# Set local variable $cc to first command line argument, if present
cc=$1
# execute the do loop while $curr is blank
while [ -z "$curr" ]
do
# test if $cc is blank. If so, prompt for a value
if [ -z "$cc" ]
then
echo -n "Enter a currency code: "
read cc
fi
# Search for $cc as a code in the curr.tab file
curr=`grep "^$cc" curr.tab`
# clear cc in case grep found no match and the loop should be repeated
cc=
done
# echo the result
echo $curr
The curr.tab I am using would look something like this:
USD US Dollar 1.0000000000 1.0000000000
EUR Euro 0.7255238463 1.3783144484
GBP British Pound 0.6182980743 1.6173428992
INR Indian Rupee 61.5600229886 0.0162443084
AUD Australian Dollar 1.0381120551 0.9632871472
CAD Canadian Dollar 1.0378792155 0.9635032527
AED Emirati Dirham 3.6730001428 0.2722570000
MYR Malaysian Ringgit 3.1596464286 0.3164911083
At the end, instead of echo $curr do:
[[ $curr =~ ^(...)[[:space:]]+(.+)([[:space:]]+([[:digit:]\.]+)){2}$ ]]
printf "Currency code: %s\nCurrency name: %s\nUnits per USD: %s\nUSD per unit : %s\n" "${BASH_REMATCH[#]:1}"

Length of string in bash

How do you get the length of a string stored in a variable and assign that to another variable?
myvar="some string"
echo ${#myvar}
# 11
How do you set another variable to the output 11?
To get the length of a string stored in a variable, say:
myvar="some string"
size=${#myvar}
To confirm it was properly saved, echo it:
$ echo "$size"
11
Edit 2023-02-13: Use of printf %n instead of locales...
UTF-8 string length
In addition to fedorqui's correct answer, I would like to show the difference between string length and byte length:
myvar='Généralités'
chrlen=${#myvar}
oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C
bytlen=${#myvar}
LANG=$oLang LC_ALL=$oLcAll
printf "%s is %d char len, but %d bytes len.\n" "${myvar}" $chrlen $bytlen
will render:
Généralités is 11 char len, but 14 bytes len.
you could even have a look at stored chars:
myvar='Généralités'
chrlen=${#myvar}
oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C
bytlen=${#myvar}
printf -v myreal "%q" "$myvar"
LANG=$oLang LC_ALL=$oLcAll
printf "%s has %d chars, %d bytes: (%s).\n" "${myvar}" $chrlen $bytlen "$myreal"
will answer:
Généralités has 11 chars, 14 bytes: ($'G\303\251n\303\251ralit\303\251s').
Nota: According to Isabell Cowan's comment, I've added setting to $LC_ALL along with $LANG.
Same, but without having to play with locales
I recently learn %n format of printf command (builtin):
myvar='Généralités'
chrlen=${#myvar}
printf -v _ %s%n "$myvar" bytlen
printf "%s is %d char len, but %d bytes len.\n" "${myvar}" $chrlen $bytlen
Généralités is 11 char len, but 14 bytes len.
Syntax is a little counter-intuitive, but this is very efficient! (further function strU8DiffLen is about 2 time quicker by using printf than previous version using local LANG=C.)
Length of an argument, working sample
Argument work same as regular variables
showStrLen() {
local -i chrlen=${#1} bytlen
printf -v _ %s%n "$1" bytlen
LANG=$oLang LC_ALL=$oLcAll
printf "String '%s' is %d bytes, but %d chars len: %q.\n" "$1" $bytlen $chrlen "$1"
}
will work as
showStrLen théorème
String 'théorème' is 10 bytes, but 8 chars len: $'th\303\251or\303\250me'
Useful printf correction tool:
If you:
for string in Généralités Language Théorème Février "Left: ←" "Yin Yang ☯";do
printf " - %-14s is %2d char length\n" "'$string'" ${#string}
done
- 'Généralités' is 11 char length
- 'Language' is 8 char length
- 'Théorème' is 8 char length
- 'Février' is 7 char length
- 'Left: ←' is 7 char length
- 'Yin Yang ☯' is 10 char length
Not really pretty output!
For this, here is a little function:
strU8DiffLen() {
local -i bytlen
printf -v _ %s%n "$1" bytlen
return $(( bytlen - ${#1} ))
}
or written in one line:
strU8DiffLen() { local -i _bl;printf -v _ %s%n "$1" _bl;return $((_bl-${#1}));}
Then now:
for string in Généralités Language Théorème Février "Left: ←" "Yin Yang ☯";do
strU8DiffLen "$string"
printf " - %-$((14+$?))s is %2d chars length, but uses %2d bytes\n" \
"'$string'" ${#string} $((${#string}+$?))
done
- 'Généralités' is 11 chars length, but uses 14 bytes
- 'Language' is 8 chars length, but uses 8 bytes
- 'Théorème' is 8 chars length, but uses 10 bytes
- 'Février' is 7 chars length, but uses 8 bytes
- 'Left: ←' is 7 chars length, but uses 9 bytes
- 'Yin Yang ☯' is 10 chars length, but uses 12 bytes
Unfortunely, this is not perfect!
But there left some strange UTF-8 behaviour, like double-spaced chars, zero spaced chars, reverse deplacement and other that could not be as simple...
Have a look at diffU8test.sh or diffU8test.sh.txt for more limitations.
I wanted the simplest case, finally this is a result:
echo -n 'Tell me the length of this sentence.' | wc -m;
36
You can use:
MYSTRING="abc123"
MYLENGTH=$(printf "%s" "$MYSTRING" | wc -c)
wc -c or wc --bytes for byte counts = Unicode characters are counted with 2, 3 or more bytes.
wc -m or wc --chars for character counts = Unicode characters are counted single until they use more bytes.
In response to the post starting:
If you want to use this with command line or function arguments...
with the code:
size=${#1}
There might be the case where you just want to check for a zero length argument and have no need to store a variable. I believe you can use this sort of syntax:
if [ -z "$1" ]; then
#zero length argument
else
#non-zero length
fi
See GNU and wooledge for a more complete list of Bash conditional expressions.
If you want to use this with command line or function arguments, make sure you use size=${#1} instead of size=${#$1}. The second one may be more instinctual but is incorrect syntax.
Using your example provided
#KISS (Keep it simple stupid)
size=${#myvar}
echo $size
Here is couple of ways to calculate length of variable :
echo ${#VAR}
echo -n $VAR | wc -m
echo -n $VAR | wc -c
printf $VAR | wc -m
expr length $VAR
expr $VAR : '.*'
and to set the result in another variable just assign above command with back quote into another variable as following:
otherVar=`echo -n $VAR | wc -m`
echo $otherVar
http://techopsbook.blogspot.in/2017/09/how-to-find-length-of-string-variable.html
I know that the Q and A's are old enough, but today I faced this task for first time. Usually I used the ${#var} combination, but it fails with unicode: most text I process with the bash is in Cyrillic...
Based on #atesin's answer, I made short (and ready to be more shortened) function which may be usable for scripting. That was a task which led me to this question: to show some message of variable length in pseudo-graphics box. So, here it is:
$ cat draw_border.sh
#!/bin/sh
#based on https://stackoverflow.com/questions/17368067/length-of-string-in-bash
border()
{
local BPAR="$1"
local BPLEN=`echo $BPAR|wc -m`
local OUTLINE=\|\ "$1"\ \|
# line below based on https://www.cyberciti.biz/faq/repeat-a-character-in-bash-script-under-linux-unix/
# comment of Bit Twiddler Jun 5, 2021 # 8:47
local OUTBORDER=\+`head -c $(($BPLEN+1))</dev/zero|tr '\0' '-'`\+
echo $OUTBORDER
echo $OUTLINE
echo $OUTBORDER
}
border "Généralités"
border 'А вот еще одна '$LESSCLOSE' '
border "pure ENGLISH"
And what this sample produces:
$ draw_border.sh
+-------------+
| Généralités |
+-------------+
+----------------------------------+
| А вот еще одна /usr/bin/lesspipe |
+----------------------------------+
+--------------+
| pure ENGLISH |
+--------------+
First example (in French?) was taken from someone's example above.
Second one combines Cyrillic and the value of some variable. Third one is self-explaining: only 1s 1/2 of ASCII chars.
I used echo $BPAR|wc -m instead of printf ... in order to not rely on if the printf is buillt-in or not.
Above I saw talks about trailing newline and -n parameter for echo. I did not used it, thus I add only one to the $BPLEN. Should I use -n, I must add 2.
To explain the difference between wc -m and wc -c, see the same script with only one minor change: -m was replaced with -c
$ draw_border.sh
+----------------+
| Généralités |
+----------------+
+---------------------------------------------+
| А вот еще одна /usr/bin/lesspipe |
+---------------------------------------------+
+--------------+
| pure ENGLISH |
+--------------+
Accented characters in Latin, and most of characters in Cyrillic are two-byte, thus the length of drawn horizontals are greater than the real length of the message.
Hope, it will save some one some time :-)
p.s. Russian text says "here is one more"
p.p.s. Working "two-liner"
#!/bin/sh
#based on https://stackoverflow.com/questions/17368067/length-of-string-in-bash
border()
{
# line below based on https://www.cyberciti.biz/faq/repeat-a-character-in-bash-script-under-linux-unix/
# comment of Bit Twiddler Jun 5, 2021 # 8:47
local OUTBORDER=\+`head -c $(( $(echo "$1"|wc -m) +1))</dev/zero|tr '\0' '-'`\+
echo $OUTBORDER"\n"\|\ "$1"\ \|"\n"$OUTBORDER
}
border "Généralités"
border 'А вот еще одна '$LESSCLOSE' '
border "pure ENGLISH"
In order to not clutter the code with repetitive OUTBORDER's drawing, I put the forming of OUTBORDER into separate command
Maybe just use wc -c to count the number of characters:
myvar="Hello, I am a string."
echo -n $myvar | wc -c
Result:
21
Length of string in bash
str="Welcome to Stackoveflow"
length=`expr length "$str"`
echo "Length of '$str' is $length"
OUTPUT
Length of 'Welcome to Stackoveflow' is 23

Resources