What is the best way to store sliced arguments in Bash? - bash

Example
>>./my_script.sh a b c
If I try to echo argument 2 - ..., I may do
>>echo "${#:2}"
a b c
And if i want to store ${#:2} in variable, these methods will not work
my_params=${#:2}
or
my_params="${#:2}"
But this way is work
my_params="$(echo ${#:2})"
I can feel an ugliness of this way. So, my questions are
What is the proper way to store a sliced arguments?
How to assign those sliced arguments to a variable?
How to reuse it as parameters of another function again?

In the original Bourne shell, only the positional argument list was available for this. Fortunately, modern derivatives have an array variable type specifically for this kind of situation.
array=("${#[2:]}") # note parentheses for array
echo "${array[0]}" # first arg of array
command "${array[#]}" # pass array as quoted arguments

Related

Programmatically create bash command with flags for items in array

I have a list/array like so:
['path/to/folder/a', 'path/to/folder/b']
This is an example, the array can be of any length. But for each item in the array I'd like to set up the following as a single command:
$ someTool <command> --flag <item-1> --flag <item-2> ... --flag <item-N>
At the moment I am currently doing a loop over the array but I am just wondering if doing them individually has a different behaviour to doing them all at once (which the tool specifies I should do).
for i in "${array[#]}"; do
someTool command --flag $i
done
Whether passing all flag arguments to a single invocation of the tool does the same thing as passing them one-at-a-time to separate invocations depends entirely on the tool and what it does. Without more information, it's impossible to say for sure, but if the instructions recommend passing them all at once, I'd go with that.
The simplest way to do this in bash is generally to create a second array with the flags and arguments as they need to be passed to the tool:
flagsArray=()
for i in "${array[#]}"; do
flagsArray+=(--flag "$i")
done
someTool command "${flagsArray[#]}"
Note: all of the above syntax -- all the quotes, braces, brackets, parentheses, etc -- matter to making this run properly and robustly. Don't leave anything out unless you know why it's there, and that leaving it out won't cause trouble.
BTW, if the option (--flag) doesn't have to be passed as a separate argument (i.e. if the tool allows --flag=path/to/folder/a instead of --flag path/to/folder/a), then you can use a substitution to add the --flag= bit to each element of the array in a single step:
someTool command "${array[#]/#/--flag=}"
Explanation: the /# means "replace at the beginning (of each element)", then the empty string for the thing to replace, / to delimit that from the replacement string, and --flag= as the replacement (/addition) string.

concatenate variables in a bash script

Hi I would like to set a variable by concatenating two other variables.
Example
A=1
B=2
12=C
echo $A$B
desired result being C
however the answer I get is always 12
Is it possible?
UPDATED
Example
A=X
B=Y
D=$A$B
xy=test
echo $D
desired result being "test"
It looks like you want indirect variable references.
BASH allows you to expand a parameter indirectly -- that is, one variable may contain the name of another variable:
# Bash
realvariable=contents
ref=realvariable
echo "${!ref}" # prints the contents of the real variable
But as Pieter21 indicates in his comment 12 is not a valid variable name.
Since 12 is not a valid variable name, here's an example with string variables:
> a='hello'
> b='world'
> declare my_$a_$b='my string'
> echo $my_hello_world
my string
What you are trying to do is (almost) called indirection: http://wiki.bash-hackers.org/syntax/pe#indirection
...I did some quick tests, but it does not seem logical to do this without a third variable - you cannot do concatenated indirection directly as the variables/parts being concatenated do not evaluate to the result on their own - you would have to do another evaluation. I think concatenating them first might be the easiest. That said, there is a chance you could rethink what you're doing.
Oh, and you cannot use numbers (alone or as the starting character) for variable names.
Here we go:
cake="cheese"
var1="ca"
var2="ke"
# this does not work as the indirection sees "ca" and "ke", not "cake". No output.
echo ${!var1}${!var2}
# there might be some other ways of tricking it to do this, but they don't look to sensible as indirection probably needs to work on a real variable.
# ...this works, though:
var3=${var1}${var2}
echo ${!var3}

Pattern matching in bash with tuple-like arguments

I want to pass a variable number of 'tuples' as arguments into a bash script and go through them in a loop using pattern matching, something like this:
for *,* in "$#"; do
#do something with first part of tuple
#do something with second part of tuple
done
is this possible? If so, how do I access each part of the tuple?
For example I would like to call my script like:
bash bashscript.sh first_file.xls,1 second_file,2 third_file,2 ... nth_file,1
Since bash doesn't have a tuple datatype (it just has strings), you need would need to encode and decode them yourself. For example:
$ bash bashscript.sh first_file.xls,1 second_file,2 third_file,2 ... nth_file,1
In bashscript.sh:
for tuple in "$#"; do
IFS=, read first second <<< "$tuple"
...
done
Yes, it's possible, and there is more than one way to do it. You can use the prefix/suffix expansion syntax on variables (e.g. ${var#prefix}, ${var##prefix}, ${var%suffix}, ${var%%suffix} - these remove either the shortest or longest prefix/suffix matching the specified pattern). Or you can replace the positional parameters with e.g. IFS=, set -- ${var} (although you'd have to make sure to save the rest of the original parameters in some way first so you can continue your loop). You can use arrays, if your version of bash is new enough (and if it isn't it's pretty old...). Those are probably three of the better methods, but there are others...
Edit: some examples using the suffix/prefix expansions:
for tuple in first_file.xls,1
do
echo ${tuple%,*} # "first_file.xls"
echo ${tuple#*,} # "1"
done
If your tuples are more than 2-ary, that method's a little more complex; for example:
for tuple in x,y,z
do
first=${tuple%%,*}
rest=${tuple#${first}}
second=${rest%%,*}
last=${rest#*,}
done
In that case you might prefer #chepner's answer of IFS=, read first second third <<< "${tuple}"... Otherwise, the bookkeeping can get hairy for large tuples. Setting an array from the tuple would be an acceptable alternative as well.
For simple pairs, though, I tend to prefer just stripping off a prefix/suffix as appropriate...

BASH Accessing the next 'field' in command line without a loop

This may seem like a silly question, but I've looked high and low for a solution and have not come up with one. I have a script that accepts a number of arguments and I simply wish to know how to have one argument and access the next immediate argument. I've tried stuff like $i+1 with no avail. This next argument will end up holding a destination directory, so I need to be able to access the contents. Also note that I am getting the argument position through a counter variable iterated in a loop, which is why I can't simply say $2 or something. I apologize for such a mundane question.
$ bash -c 'foo=1 ; echo ${#:$((foo+2)):1}' a b c d e f g
d
If i is a variable containing an integer, and you want the (i+1)st argument, you can do:
eval arg=\$$(( i + 1 ))
Now arg contains the argument you want
It might not be applicable in your case, but if you are accessing the arguments sequentially, you could use the inbuilt shift command to shift through the arguments.
Doing shift with no argument will set $1 to $2, $2 to $3 and so on, discarding the old value of $1. Giving a numerical argument to shift will shift that many steps.

bash command expansion

The following bash command substitution does not work as I thought.
echo $TMUX_$(echo 1)
only prints 1 and I am expecting the value of the variable $TMUX_1.I also tried:
echo ${TMUX_$(echo 1)}
-bash: ${TMUXPWD_$(echo 1)}: bad substitution
Any suggestions ?
If I understand correctly what you're looking for, you're trying to programatically construct a variable name and then access the value of that variable. Doing this sort of thing normally requires an eval statement:
eval "echo \$TMUX_$(echo 1)"
Important features of this statement include the use of double-quotes, so that the $( ) gets properly interpreted as a command substitution, and the escaping of the first $ so that it doesn't get evaluated the first time through. Another way to achieve the same thing is
eval 'echo $TMUX_'"$(echo 1)"
where in this case I used two strings which automatically get concatenated. The first is single-quoted so that it's not evaluated at first.
There is one exception to the eval requirement: Bash has a method of indirect referencing, ${!name}, for when you want to use the contents of a variable as a variable name. You could use this as follows:
tmux_var = "TMUX_$(echo 1)"
echo ${!tmux_var}
I'm not sure if there's a way to do it in one statement, though, since you have to have a named variable for this to work.
P.S. I'm assuming that echo 1 is just a stand-in for some more complicated command ;-)
Are you looking for arrays? Bash has them. There are a number of ways to create and use arrays in bash, the section of the bash manpage on arrays is highly recommended. Here is a sample of code:
TMUX=( "zero", "one", "two" )
echo ${TMUX[2]}
The result in this case is, of course, two.
Here are a few short lines from the bash manpage:
Bash provides one-dimensional indexed and associative array variables. Any variable may be
used as an indexed array; the declare builtin will explicitly declare an array. There is
no maximum limit on the size of an array, nor any requirement that members be indexed or
assigned contiguously. Indexed arrays are referenced using integers (including arithmetic
expressions) and are zero-based; associative arrays are referenced using arbitrary
strings.
An indexed array is created automatically if any variable is assigned to using the syntax
name[subscript]=value. The subscript is treated as an arithmetic expression that must
evaluate to a number greater than or equal to zero. To explicitly declare an indexed
array, use declare -a name (see SHELL BUILTIN COMMANDS below). declare -a name[subscript]
is also accepted; the subscript is ignored.
This works (tested):
eval echo \$TMUX_`echo 1`
Probably not very clear though. Pretty sure any solutions will require backticks around the echo to get that to work.

Resources