patternbashMinor
Mixing function parameters, commands and command arguments in Bash functions
Viewed 0 times
functionsargumentscommandsfunctionmixingandcommandparametersbash
Problem
I'm trying to write a bash function that will take a command such as
Edit 1:
Originally the idea came from me having to copy/paste these same lines within different conditional blocks...
xcode-select -v or brew -v that outputs a lot of extra text, and format it to my liking. Just to further the example, say I run xcode-select -v, it will output xcode-select version 2333.. I want to be able to split this string up and only take the 2333 part so I can put it in an echo statement. I want the same function to be able to handle the various outputs of stuff like brew -v, or git --version, etc.get_version_number() {
# set local variable to executed arguments that is passed in
local command="$($@)"
# set up temp variable and assign to global IFS variable
OIFS=$IFS
# set up IFS to split the string on empty space
IFS=" "
# read commands output into an array and store it in $array variable
read -a array <<< "$command"
# clear out IFS back to what it original was for further use
IFS=$OIFS
# echo out array at a particular indices that should be passed in
echo ${array[@]}
}
# store output of this command into variable for further usage
version=$(get_version_number xcode-select -v)
echo $versionEdit 1:
Originally the idea came from me having to copy/paste these same lines within different conditional blocks...
command=$(git -v)
OIFS=$IFS
IFS=" "
read -a output_array <<< "$command"
IFS=$OIFS
printf "Git version ${output_array[2]} is installed."
command=$(brew -v)
OIFS=$IFS
IFS=" "
read -a output_array <<< "$command"
IFS=$OIFS
printf "Homebrew version ${output_array[1]} is installed."Solution
The trick for splitting up the version blurb for just the version number, is to pass the values you want in before the command, and then shift them off
you have done a relatively unfamiliar-to-me parsing mechanism with the resetting the field separators, etc. Using bash as a parser is a problem for me (and I've never hated myself enough to try....).
I would set up a function that takes a co-ordinate of a version as a line/word combination, and rely on out-of-bash tools to do it......
I will probably get nailed for starting 4 sub-processes, but head, tail, and cut are small.... right? (Forgive me already, you won't be calling this in a tight loop, will you? You're already doing that a bit with the bash
Then you would call it like:
which for me, produces:
Git and Perl are 1.7.1 and v5.10.1 respectively
This also allows you to do things like get the redhat version as
and even things like the current time
Some notes:
If you wanted, for example, to save one process each time, you could instead do:
That saves using the
Edit 2.
'Obviously', what I suggest you do is not necessarily the best thing. Your code was working fine, and the mechanism will be faster (slightly) than mine because it does not do the additional 4 processes and keeps the logic inside bash internal commands.
Here is my 'shift' system applied to your code:
and you would call it with the word-number for the version as the first argument:
But this does not allow you to find versions on multi-line outputs.....
$@.you have done a relatively unfamiliar-to-me parsing mechanism with the resetting the field separators, etc. Using bash as a parser is a problem for me (and I've never hated myself enough to try....).
I would set up a function that takes a co-ordinate of a version as a line/word combination, and rely on out-of-bash tools to do it......
I will probably get nailed for starting 4 sub-processes, but head, tail, and cut are small.... right? (Forgive me already, you won't be calling this in a tight loop, will you? You're already doing that a bit with the bash
$( ...) operator...)get_version_number() {
local line=$1
shift
local word=$1
shift
# echo Line $line Word $word and Commend $@
# set local variable to executed arguments that is passed in
echo "$( $@ | head -$line | tail -1 | cut -d ' ' -f $word)"
}Then you would call it like:
gitver=$(get_version_number 1 3 git --version)
perlver=$(get_version_number 2 4 perl -version)
echo Git and Perl are $gitver and $perlver respectivelywhich for me, produces:
Git and Perl are 1.7.1 and v5.10.1 respectively
This also allows you to do things like get the redhat version as
rhelver=$(get_version_number 1 7 cat /etc/issue)
javaver=$(get_version_number 1 3 java -version)and even things like the current time
23:45:08 ;-)time=$(get_version_number 1 4 date)Some notes:
- historically, it is 'expensive' to start lots of child processes. It still is a problem, but not quite as bad as it was. Doing that hundreds or thousands of times may add up to seconds of time wasted. If you can avoid creating child processes, do it.... but, sometimes it's just easier to be lazy than to be super-bash-smart.
- I create an additional 4 processes (actually 5) each time you call the function. One for the
$( ... ), one for the$@inside that, one forhead, one fortail, and one forcut. That is almost excessive.... but if you are calling this 100 times, then it's OK, if you are calling it 1000 times, you will be able to save seconds by doing it differently.
- the
$( ... )operator is formally called the Command Substitution. What it means is that everything inside the structure is run as if in a 'child' bash process.... (so a new process is created, and the output is substituted in place of the command)
- what the
shiftoperator does is take $@ and remove the first item.... so, your function called asget_version_number 1 3 git --versiongets 4 parameters,1,3,gitand--version. We copy$1off as$line, and then shift $@, so there are now only 3 parameters in$@. We then copy$1again (but because of the shift, it is a different value) as$word, and then shift again to remove it. We are left with justgitand--versionas the parameters.
If you wanted, for example, to save one process each time, you could instead do:
get_version_number() {
local line=$1
shift
local word=$1
shift
GOTVERSION="$( $@ | head -$line | tail -1 | cut -d ' ' -f $word)"
}
get_version_number 1 3 java -version
javaversion=$GOTVERSIONThat saves using the
$( .... ) and echo combination, so you save a process, but the code is a bit more complicated.Edit 2.
'Obviously', what I suggest you do is not necessarily the best thing. Your code was working fine, and the mechanism will be faster (slightly) than mine because it does not do the additional 4 processes and keeps the logic inside bash internal commands.
Here is my 'shift' system applied to your code:
get_version_number() {
local word=$1
shift
# set local variable to executed arguments that is passed in
local command="$($@)"
# set up temp variable and assign to global IFS variable
OIFS=$IFS
# set up IFS to split the string on empty space
IFS=" "
# read commands output into an array and store it in $array variable
read -a array <<< "$command"
# clear out IFS back to what it original was for further use
IFS=$OIFS
# echo out array at a particular indices that should be passed in
echo ${array[$word]}
}and you would call it with the word-number for the version as the first argument:
gitver=$(get_version_number 3 git --version)But this does not allow you to find versions on multi-line outputs.....
Code Snippets
get_version_number() {
local line=$1
shift
local word=$1
shift
# echo Line $line Word $word and Commend $@
# set local variable to executed arguments that is passed in
echo "$( $@ | head -$line | tail -1 | cut -d ' ' -f $word)"
}gitver=$(get_version_number 1 3 git --version)
perlver=$(get_version_number 2 4 perl -version)
echo Git and Perl are $gitver and $perlver respectivelyrhelver=$(get_version_number 1 7 cat /etc/issue)
javaver=$(get_version_number 1 3 java -version)time=$(get_version_number 1 4 date)get_version_number() {
local line=$1
shift
local word=$1
shift
GOTVERSION="$( $@ | head -$line | tail -1 | cut -d ' ' -f $word)"
}
get_version_number 1 3 java -version
javaversion=$GOTVERSIONContext
StackExchange Code Review Q#40508, answer score: 3
Revisions (0)
No revisions yet.