I am trying to convert a numerical date to an alphabetical date in BASH without using the date function (legacy BASH environment).
What I am trying to do is have a case statement of each month, set it to a variable (ConvDate) based on the input TestDate and then return it in the OutputDate variable. However, when I test this, it prints the date rearranged without adding the string to it:
Sample in:
10/25/18
Sample out as of now:
25//18
Some thoughts about your code:
in the branches of the case command, you are attempting to execute the Jan command, when you need to assign that string to a variable.
you need to parse the datestring before the case command, since you clearly need to know the month number first.
braces are not quotes: be careful with how you quote your variables (i.e. always quote them) -- this will not hurt you here, unless you actually get the input data from the user.
you are handling the month number as a string here. If, as your script progresses, you start to handle it as a number, be careful that 08 and 09 are invalid octal numbers and bash will be happy to let you know:
$ m=08; echo $(($m+1))
bash: 08: value too great for base (error token is "08")
revised
TestDate=09/25/18
IFS=/ read m d y <<INPUT
$TestDate
INPUT
case $m in
01) Mon=Jan ;;
02) Mon=Feb ;;
03) Mon=Mar ;;
04) Mon=Apr ;;
05) Mon=May ;;
06) Mon=Jun ;;
07) Mon=Jul ;;
08) Mon=Aug ;;
09) Mon=Sep ;;
10) Mon=Oct ;;
11) Mon=Nov ;;
12) Mon=Dec ;;
esac
OutputDate="$d/$Mon/$y"
echo "$OutputDate"
You're nearly there. When you say case ${ConvDate} in this means "When ${ConvDate} is...". You should replace ${ConvDate} with what you're actually testing (so characters 0 to 1 of the date string, or ${TestDate:0:2}), and then inside the case statements do the assignment:
ConvDate=Jan
Related
This question already has answers here:
How do I parse command line arguments in Bash?
(40 answers)
Closed 3 years ago.
I am very new to shell scripting and using CLI all together. However, I want to create a simple program that can collect arguments from a user.
Optimally, I want my shell script to get a network name and instance name from the user, and assign the values to variables.
I know that $# can be used to gather arguments, but are there any other ways? I often notice when I make a command, there is, for example, something like this: $create --instance_name NAME . Can I gather an argument by using -- to specify the parameter? If so, here is an example of the command:
$collect_variable.sh --network NETWORK_NAME --instance_name INSTANCE_ID
Once again, thank you for any help. I am very new to stack overflow and unix all together, and any help is appreciated.
You can using a variable to assign to for example:
while getopts "x:y:z:" res
do
case "$res" in
x ) paramX="$OPTARG" ;;
y ) paramY="$OPTARG" ;;
z ) paramX="$OPTARG" ;;
? ) echo "not a valid value"
esac
done
Check the rest of the links that other StackO users sent, they provide great examples!
Hope that can help you solve your issue.
Peace!
There are lots of ways of gathering arguments. You can name their position, e.g. $1 is the first argument and $14 is the fourteenth.
You can refer to the argument list like $# (this preserves spacing) or like $* (this collapses spacing):
test_args() {
echo '# as "$#"'
for argument in "$#"; do
echo "$argument"
done
echo '# as "$*"'
for argument in "$*"; do
echo "$argument"
done
echo '# as $*'
for argument in $*; do
echo "$argument"
done
}
$ test_args "1 2" three four
# as "$#"
1 2
three
four
# as "$*"
1 2 three four
# as $*
1
2
three
four
Since you're exclusively using long options separated from their arguments by spaces,
while [ "$#" -gt 0 ]; do
case "$1" in
( --network ) NETWORK="$2"; shift ;;
( --network=?* ) NETWORK="${1#*=}" ;;
( --instance_name ) INSTANCE_NAME="$2"; shift ;;
( --instance_name=?* ) INSTANCE_NAME="${1#*=}" ;;
( -* ) echo "Illegal option -- $1" >&2; exit 2 ;;
( * ) break ;; # perhaps non-options are used later
esac
shift
done
This loops on each option and parses it. There are two conditions for each option, which lets us handle when the arguments are spaced from the options (--network bobnet) or when they're assigned to the options (--network=bobnet). Spaced means we need to refer to the next argument (thus the extra shift) while assigned means we need to use parameter subsitution to remove the characters at the front of the string up until (and including) the first =.
The final shift pulls the first item off of the argument list so we can operate on the next one. Shifting separately for the two-argument clauses (rather than ending with shift 2) also allows for binary options like --verbose, which doesn't contain an argument.
You can also use a while getopts loop to support a mix of short and long arguments; see my more detailed post on getopts.
I am trying to assign a variable from a prompt input choice with no luck. If the user inputs 1, I want target_db_name = "database2".
My code:
while true; do
read -p "What is the table name?" table_name
table_name=${table_name,,}
if hdfs dfs -test -e /foo/$table_name ;
then read -p "What is the target database you want to copy the
“foo.${table_name}” table to?
Your three options are:
1) database1
2) database2
3) database3
Type 1, 2, or 3: " target_db;
(((Here is where I want to state if $target_db = "1" then target_db_name
= "database1", if $target_db = "2" then target_db_name = "database2" etc...)))
read -p "Would you like to begin the HDFS copy with the following configuration:
Target Database: ${target_db_name}
Table Name: ${table_name}
Continue (Y/N):"
else echo "Please provide a valid table name.
Exiting this script" ; exit ; fi
done
I have tried creating another if statement with no luck.
"....Type 1, 2, or 3: " target_db;
else if $target_db = "1" then target_db_name = "edw_qa_history"; fi
if $target_db = "1" then won't work, because what follows if must be a command, not a test expression. Now, the most common command used in if statements is [ (yes, that's actually a command name; it's synonymous with the test command), which takes a test expression (and a close bracket) as its arguments and succeeds or fails depending on whether the expression is true or not. So the correct syntax would be something like:
if [ "$target_db" = "1" ]; then
Note that there are two other differences from what you had: I put double-quotes around the variable reference (almost always a good idea, to avoid may parsing oddities), and added a semicolon before then (needed to indicate where the arguments to [ end and shell syntax resumes). I also notice you have semicolons at the end of many lines of your script; this isn't necessary, the end-of-line is enough to indicate the end of a command. It's only if you have another command (or something like then) on the same line that you need a semicolon as a delimiter.
HOWEVER, as #Barmar pointed out in a comment, case would probably be better than a list of if and elif statements here. case is intended specifically for comparing a string against a list of other strings (or patterns), and executing different things depending on which one it matches. It looks something like this:
case "$target_db" in
1) target_db_name="database1" ;;
2) target_db_name="database2" ;;
3) target_db_name="database3" ;;
*) "Please provide a valid table name. Exiting this script" ; exit ;;
esac
Here, the double-semicolon is needed, even at the end of a line, to indicate the end of each case. Also, note that the * pattern (the last case) matches anything, so it functions like an else would in an if ... elif ... sequence.
Final note: use shellcheck.net to sanity-check your code.
You don't need an if statement to map the number to an array; you just need an array.
db_names=(
"datebase 1"
"database 2"
"database 3"
)
# ...
target_db_name=${db_names[$target_db - 1]}
if [[ -z $target_db_name ]]; then
exit
fi
I want a variable whose value should be in already defined range.
for e.g.
variable DAY=$1
should only get values Mon,Tue,Wed,Thus,Fri,Sat,Sun.
if user enter something else, it will not accept it.
I know i can do this by defining array or normally a variable storing all the days and then check variable DAY in a "for or while" loop.
But i want the simplest way without loop.
Thanks in advance.
Use the extended glob support to match exactly one of a list of choices.
range="Mon|Tue|Wed|Thu|Fri"
read day
if [[ $day = #($range) ]]; then
echo "$day is valid"
else
echo "$day is not valid"
fi
Without using a loop you can do something like this:
EOL=$'\n'
arr=(Mon Tue Wed Thus Fri Sat Sun)
read -p 'Enter day value: ' day
Tue
[[ $(printf "$EOL%s$EOL" "${arr[#]}") == *"$EOL$day$EOL"* ]] &&
echo "in range" || echo "not in range"
in range
read -p 'Enter day value: ' day
ue We
[[ $(printf "$EOL%s$EOL" "${arr[#]}") == *"$EOL$day$EOL"* ]] &&
echo "in range" || echo "not in range"
not in range
There's really no need for an array.
read -r day
case ' Mon Tue Wed Thu Fri Sat Sun ' in
*" $day "*)
echo "in range";;
*) echo "not in range";;
esac
Notice the spaces around the string expressions.
If you need to be strict, you should reject "Tue Wed" which this doesn't do. It would require a second case wrapper, which is clunky but not completely unwieldy.
To expand slightly on anubhava's answer:
arr=(Mon Tue Wed Thus Fri Sat Sun)
read -p 'Enter day value: ' day
case "${arr[#]}" in *"$day"*) echo "in range";; esac
Basically, this is building an array, turning it into a string, and doing a pattern match on it.
In the context of a case expression, there's no difference between "${arr[#]}" and "${arr[*]}"; I personally would stick with *, which makes it clearer that you're building one big string instead of a list of separate ones.
With this specific set of values, you don't have to worry about substring overlap, but the above check is still overly forgiving. For instance, if you enter u, it will be considered "in range". That may not be what you want.
A more exact test would be something like this:
case "${arr[*]}" in
"$day "*|*" $day "*|*" $day") echo "in range";;
*) echo "not in range";;
esac
This still permits the user to enter multiple days as "Mon Tue" or similar; an easy way to fix that in this particular case is to change the read line from read day to read day _.
The "$day "* pattern will match at the beginning of the array (so if they enter Mon in this case). The *" $day" pattern will match at the end of the array (Fri), and the *" $day "* pattern will match the rest. Nothing else but an exact day string will match.
Also, here you can see how to handle the else case - a pattern of * matches anything that hasn't already matched something else, so that's the equivalent of else in a case.
I want my calendar to correctly handle months with leading zeros
for example: "cal 01" or "cal 01 2012"
How do I write the code to make my calendar to correctly handle months with leading zeros?
This is my code so far:
$ cat cal
#cal: nicer interface to /usr/bin/cal
case $# in
0) set 'data';m=$2; y=$6 ;; # no argu: use today
1) m=$1; set 'data'; y=$6 ;; # 1 rg: use this year
*) m=$1; y=$2 ;; # 2 ags: month and year
esac
case $m in
jan*|Jan*) m=1 ;;
feb*|Feb*) m=2 ;;
mar*|Mar*) m=3 ;;
apr*|Apr*) m=4 ;;
may*|May*) m=5 ;;
jun*|Jun*) m=6 ;;
jul*|Jul*) m=7 ;;
aug*|Aug*) m=8 ;;
sep*|Sep*) m=9 ;;
oct*|Oct*) m=10 ;;
nov*|Nov*) m=11 ;;
dec*|Dec*) m=12 ;;
[1-9]|10|11|12) ;; # numeric month
*) y=$m; m="" ;; # plain year
esac
/usr/bin/cal $m $y # run the real one
$
You can do multiple regex matching in your case statement, i.e.
case $m in
01|1|jan*|Jan*) m=1 ;;
02|2|feb*|Feb*) m=2 ;;
....
Else, you could use shell parameter substitution to remove any leading 0's, i.e.
# as a way to demonstrate param sub on $1 etc, load values to $1 and $2
set -- 01 02
echo ${1#0}
echo ${2#0}
# output
1
2
Edit
For your follow-up question
Example, the current month is November, 2005, if you run "cal 01", you should print out the calendar of Jan. 2006
Try this:
# if the month input is less than the current month, assume the next year
if (( ${y:-0} == 0 && m < $(/bin/date +%m) )) ; then
y=$(/bin/date +%Y)
((y++))
fi
${y:-0} is one of several parameter checking syntaxs provided by most shells that allows a default value to be substituted if the var value is completely unset (not set at all) or = "". So in this case, if y wasn't set by the command line, it will appear as 0 in this evaluation, allowing the && section to be be executed to test the month, etc.
You'll need to extend your case $# processing to allow for 1 argument, that is assumed to be a month value.
I hope this helps.
Does exist in linux bash something similar to the following code in PHP:
list($var1, $var2, $var3) = function_that_returns_a_three_element_array() ;
i.e. you assign in one sentence a corresponding value to 3 different variables.
Let's say I have the bash function myBashFuntion that writes to stdout the string "qwert asdfg zxcvb".
Is it possible to do something like:
(var1 var2 var3) = ( `myBashFuntion param1 param2` )
The part at the left of the equal sign is not valid syntax of course. I'm just trying to explain what I'm asking for.
What does work, though, is the following:
array = ( `myBashFuntion param1 param2` )
echo ${array[0]} ${array[1]} ${array[2]}
But an indexed array is not as descriptive as plain variable names.
However, I could just do:
var1 = ${array[0]} ; var2 = ${array[1]} ; var3 = ${array[2]}
But those are 3 more statements that I'd prefer to avoid.
I'm just looking for a shortcut syntax. Is it possible?
First thing that comes into my mind:
read -r a b c <<<$(echo 1 2 3) ; echo "$a|$b|$c"
output is, unsurprisingly
1|2|3
I wanted to assign the values to an array. So, extending Michael Krelin's approach, I did:
read a[{1..3}] <<< $(echo 2 4 6); echo "${a[1]}|${a[2]}|${a[3]}"
which yields:
2|4|6
as expected.
I think this might help...
In order to break down user inputted dates (mm/dd/yyyy) in my scripts, I store the day, month, and year into an array, and then put the values into separate variables as follows:
DATE_ARRAY=(`echo $2 | sed -e 's/\// /g'`)
MONTH=(`echo ${DATE_ARRAY[0]}`)
DAY=(`echo ${DATE_ARRAY[1]}`)
YEAR=(`echo ${DATE_ARRAY[2]}`)
Sometimes you have to do something funky. Let's say you want to read from a command (the date example by SDGuero for example) but you want to avoid multiple forks.
read month day year << DATE_COMMAND
$(date "+%m %d %Y")
DATE_COMMAND
echo $month $day $year
You could also pipe into the read command, but then you'd have to use the variables within a subshell:
day=n/a; month=n/a; year=n/a
date "+%d %m %Y" | { read day month year ; echo $day $month $year; }
echo $day $month $year
results in...
13 08 2013
n/a n/a n/a
Chapter 5 of the Bash Cookbook by O'Reilly, discusses (at some length) the reasons for the requirement in a variable assignment that there be no spaces around the '=' sign
MYVAR="something"
The explanation has something to do with distinguishing between the name of a command and a variable (where '=' may be a valid argument).
This all seems a little like justifying after the event, but in any case there is no mention of a method of assigning to a list of variables.
let var1=var2=var3=0
or
var1=var2=var3="Default value"