Hello All,
In the below script i am trying to check and list the file names, get the last file with highest version number and then increment the version number when i create another file. Example: file1 is COBANK_v1.xml and file2 i want to put it as COBANK_v2.xml, to achieve this i am using awk and want to do an expression later to increment the number. Can you please help.
#!/bin/bash
. /home/infrmtca/bin/dwh_Loadfuncs.sh
PROVIDER=$1
case $PROVIDER in
1) FILENAME="AAC";;
4) FILENAME="AGFIRST";;
5) FILENAME="AGRIBANK";;
6) FILENAME="COBANK";;
7) FILENAME="COLUSA-GLENN";;
8) FILENAME="FCSA";;
11) FILENAME="HAWAII";;
12) FILENAME="IDAHO";;
16) FILENAME="FCBTEXAS";;
17) FILENAME="WAC";;
18) FILENAME="AGVANTIS";;
20) FILENAME="YOSEMITE";;
21) FILENAME="FPI";;
*) FILENAME="Not Valid";;
esac
echo $FILENAME
if [ -f "$TGT_DIR"/"$FILENAME"*.xml ]
then
echo "success"
for i in "$TGT_DIR"/"$FILENAME"*.xml
do
basename "$i" | awk -F"." '{print $2}'
done
else
echo "Failure"
fi
Even this uses ls and head and makes the wild assumption that the latest version of any Provider's files will be the one with the most recent timestamp. It also assumes that there will be at least one existing file for each provider. The following script is much more complex than balajesuri's script but will create version 1 of a file for a provider that doesn't currently have any files, will use the highest numbered version rather than the last touched version of a Provider's files if more than one exists, and will print a diagnostic and exit if no provider code is specified or if an unknown provider code is specified:
#!/bin/bash
IAm=${0##*/} # Last component of script name for diagnostics
Usage='Usage: %s Provider_Code
Provider_Code "%s" unknown
Valid Provider_Code values are:
Provider_Code Provider
============= ============
1 AAC
4 AGFIRST
5 AGRIBANK
6 COBANK
7 COLUSA-GLENN
8 FCSA
11 HAWAII
12 IDAHO
16 FCBTEXAS
17 WAC
18 AGVANTIS
20 YOSEMITE
21 FPI
'
. /home/infrmtca/bin/dwh_Loadfuncs.sh
#TGT_DIR=. # Assume that TGT_DIR is defined by above sourced script.
case "$1" in
(1) FILENAME="AAC";;
(4) FILENAME="AGFIRST";;
(5) FILENAME="AGRIBANK";;
(6) FILENAME="COBANK";;
(7) FILENAME="COLUSA-GLENN";;
(8) FILENAME="FCSA";;
(11) FILENAME="HAWAII";;
(12) FILENAME="IDAHO";;
(16) FILENAME="FCBTEXAS";;
(17) FILENAME="WAC";;
(18) FILENAME="AGVANTIS";;
(20) FILENAME="YOSEMITE";;
(21) FILENAME="FPI";;
(*) printf "$Usage" "$IAm" "$1" >&2
exit 1;;
esac
echo "Supplied Input FileName: $FILENAME"
MAX_V=0 # Set highest version seen for the Provider specified by $FILENAME.
for i in "$TGT_DIR/$FILENAME"*.xml
do if [ "$MAX_V" == 0 ] && [ "$i" != "${i#*[*]}" ]
then # No matches were found, set up to create 1st file for this
# Provider. ($FILENAME and $MAX_V are already set
# appropriately, so just get out.)
break
fi
# Strip off $TGT_DIR/$FILENAME and "_v"
VER_NUM=${i##*v}
# Strip off the .xml to get just the version number.
VER_NUM=${VER_NUM%.*}
# If this version number is > than previously seen versions, save it.
# This make the assumption that the version # will be numeric; if a
# user might create a file with a clashing unexpected name, the code
# should be extended to verify that $VER_NUM is a numeric string.
[ "$VER_NUM" -gt "$MAX_V" ] && MAX_V=$VER_NUM
done
echo "First Part Of New File Name: $FILENAME"
echo "Versioin Of Current File: $MAX_V"
NEW_FILE_NAME="${FILENAME}_v$((MAX_V + 1)).xml"
echo "New Versioned File Name: "$NEW_FILE_NAME
Note that although this script uses $!/bin/bash, this code will work with any POSIX conforming shell (including, but not limited to, bash and ksh) as long as nothing bash specific appears in the sourced script.
Fear of pipes can and should be overcome! But some tools like basename are silly, dated, when you can just ${full_path##*/} and save a fork() and exec() .
Thanks Don Awesome!!, i tested the script it's working fine with no single error. How ever i have few questions.
1) Is "${i#*[*]}" same as "${i#**}" when i tested it is giving same results, may i please know the purpose of those square brackets then, additional check or something?
2) [ "$VER_NUM" -gt "$MAX_V" ] && MAX_V=$VER_NUM
Here it will check if VER_NUM is greater than previous version and if it is then save it to MAX_V, probably another syntax or way of assigning a value to variable.
3) IAm=${0##/} --- Trying to debug this but don't understand completely. As per my assumption you were using this in conjunction with printf to print the output? may i please know what does each of those${0##/} represent?
4) printf "$Usage" "$IAm" "$1" >&2 --- i know you are redirecting to standard output, and you are passing Usage & IAm variables along with input argument(provider_code). Much appreciate if you could explain a bit.
1) The square brackets ensure * is a literal not a wild card metacharacter.
2) Using boolean as procedural control of flow amuses some, but I like to use procedural language for control of flow, for maintainability. Isn't the new version max plus 1?
3) ${0##*/} says take $0, pound really hard on the nose until it is removed down to the last slash, or in other words, basename, entry name of $0. One # means pound soft, stop at first / or whatever follows. The alternative operator is %, as in you get your percent in the end, so it beats on the end not not nose. The directory containing $0 would be ${0%/*} , take off the end to the first /. Of course, if $0 has no /, that malfunctions.
4) Not standard output >&1 or nothing, but standard error >&2 Printf takes first a template (usage) with meta-strings in it that say where to insert the additional parameters. Printf "A%sC", "B" prints ABC.
I'm not sure if this comment was directed at my suggestion to eliminate the ls|head pipeline. For the record, I am not on a mission to get rid of pipelines. And, when speed isn't a critical issue in a shell script, I sometimes use a pipeline (even when avoiding it could produce faster code) if the pipeline is "easier to read and understand."
But, when globbing could yield a fork error due to ARG_MAX limits, and an ls -t | head -1 pipeline would sometimes yield the wrong file and result in the destruction of data, I much prefer a different solution that avoids both issues.
Note also that the basename and dirname utilities aren't quite as easy to replace as you're suggesting. The commands:
ddir=$(dirname "$1")
sdir=${1%/*}
bbase=$(basename "$1")
sbase=${1##*/}
printf "directory containing \"%s\" is \"%s\" or \"%s\"\n" "$1" "$ddir" "$sdir"
printf "last component of \"%s\" is \"%s\" or \"%s\"\n" "$1" "$bbase" "$sbase"
show that they produce identical results when this script is invoked with the name of my home directory:
directory containing "/Users/dwc" is "/Users" or "/Users"
last component of "/Users/dwc" is "dwc" or "dwc"
but, very different results if I invoke the same script with / as an operand:
directory containing "/" is "/" or ""
last component of "/" is "/" or ""
I can certainly modify the script above to check for the special case when the operand is root; but I will frequently use basename and dirname because I don't have to think about the special case (even though a fork and exec is involved). And, what is going on will be more obvious to naive readers of my code. If this is done once during the initialization of a long running script, I might not care about the small additional start up costs. However, if this were to be done every time in a tight loop processing thousands of files that might include "/", it would be a different issue.
Yes, '/' is often a painful exception in many algorithms! People who enter directories with a trailing '/' can also mess you up. For instance, on some systems "find /tmp/" produces paths like "/tmp//xxx" . Of course, you can clean then with ${xxx%/} if they are not '/' (root).
And when i run the script "callversion.sh" it is giving me the following output, How can i make it print "version.sh" instead of "callversion.sh". I guess i don't have any other simple option other than replacing the {0##*/} with actual script name. please let me know if this is possible any other means. Thank you.
callversion.sh is invoking the version.sh, those are 2 different scripts i want the script to print it as version.sh instead of callversion.sh, if i move it i will replace one with another.
if version calls callversion, you could source it instead (. filename), and then $0 would be version. Better is to make the callversion bits functions inside version, either explicitly or by sourcing the callversion file that is rewritten to be all functions.
Hi Ariean,
I believe that DGPickett has already answered your questions. I'll go into a little more detail here since it is clear that you are a programming novice.
1) In the command line:
for i in "$TGT_DIR/$FILENAME"*.xml
the "$TGT_DIR/$FILENAME"*.xml will expand to a list of all of the files in the directory named by the expansion of "$TGT_DIR and $FILENAME . For purposes of this discussion, assume that $TGT_DIR expands to /dir and $FILENAME expands to ACC . If there are no matching files in that directory, the expansion of that filename matching pattern (and the value stored in the shell variable i the 1st (and only) time through the for loop) will be the string /dir/ACC*.xml . Then the test command:
[ "$i" != "${i#*[*]}" ]
will return TRUE (exit status 0) if the expansion of i is not the same as the expansion of i with the shortest string of any number of any characters and an asterisk character removed from the start of the expansion of i . So, in this case /dir/ACC*.xml will not be the same as .xml because there was an asterisk ( * ) in $i . Again, in this case, "${i#**}" (which expands to an empty string) is VERY different from "${i#*[*]}" (which expands to .xml ). As DGPickett said, in a filename pattern matching expression, * is a metacharacter when it is not between square brackets. In this case it matches zero or more of any character. Inside square brackets [*], it only matches the asterisk itself.
In the more common case where at least one file matches the pattern, the value stored in i will not contain an asterisk.
2) The constructs:
[ "$VER_NUM" -gt "$MAX_V" ] && MAX_V=$VER_NUM
and
if [ "$VER_NUM" -gt "$MAX_V" ]
then MAX_V=$VER_NUM
fi
are equivalent. You should be aware of both forms although I would never suggest that it would be wrong to always use the if, then, fi version. When there are imbedded comments in the then portion of an if, as there were in an earlier if statement in that for loop, you have to be very careful not to accidentally comment out the code you want to run as well when you use the first form above.
Use whichever form is easiest for you to understand.
3) In standards conforming shells, $0 expands to the pathname the shell used to invoke this script. If your script is named version.sh, then $0 could expand to something like version.sh , ./version.sh , or /home/ariean/bin/version.sh . The assignment IAM={0##*/} (similar to the "${i#*[*]}" above) removes the longest (because it uses ## instead of #) string on any number of characters ending with a "/" from the start of the contents of $0. So, for all of the possible values of $0 shown above, ${0##*/} expands to version.sh .
With both ${var#pattern} and ${var##pattern} , var will be expanded unchanged if the given pattern does not fully match a string at the start of the contents of var.
4) I don't have anything to add to what DGPickett already said here.
I see that you have posted another question while I've been working on this response. It looks like DGPickett and RudiC have already made great suggestions about how to get the correct name in your diagnostic messages.