Add prefix/suffix to all elements in space-separated string - bash

Consider you have a shell variable foo, whose value is
echo ${foo}
# Output: elementA elementB elementC
Now I would like to add same prefix __PREFIX__ and suffix __SUFFIX__ to the elements, so that
echo ${new_foo}
# Output: __PREFIX__ElementA__SUFFIX__ __PREFIX__ElementB__SUFFIX__ __PREFIX__ElementC__SUFFIX__
What is the simplest way to achieve that?
Because I'm not sure how such an operation should be called, the title is probably not describing the problem correctly.
Thanks for the comments and answers. The title has been updated.

If you have a proper array,
foo=(a b c)
you can add a prefix using the /# operator and add a suffix with the /% operator. It does have to be done in two steps, though.
$ foo=(a b c)
$ foo=("${foo[#]/#/__PREFIX__}")
$ foo=("${foo[#]/%/__SUFFIX__}")
$ declare -p foo
declare -a foo=([0]="__PREFIX__a__SUFFIX__" [1]="__PREFIX__b__SUFFIX__" [2]="__PREFIX__c__SUFFIX__")
If you just have a space-separated string, you can use //:
$ foo="a b c"
$ foo="__PREFIX__${foo// /__SUFFIX__ __PREFIX__}__SUFFIX__"
$ echo "$foo"
__PREFIX__a__SUFFIX__ __PREFIX__b__SUFFIX__ __PREFIX__c__SUFFIX__

With sed you could do:
prefix=__PREFIX__
suffix=__SUFFIX__
new=$(sed -E "s/(\S)(\s|$)/\1$suffix /g;s/(\s|^)(\S)/$prefix\2/g" <<< $foo)
which outputs:
__PREFIX__elementA__SUFFIX__ __PREFIX__elementB__SUFFIX__ __PREFIX__elementC__SUFFIX__

Here's an easy to read approach, but it's probably the worst from an efficiency standpoint.
foo="elementA elementB elementC"
PREFIX=__PREFIX__
SUFFIX=__SUFFIX__
for f in ${foo}
do
new_foo="${new_foo} ${PREFIX}${f}${SUFFIX}"
done
echo ${new_foo}

Related

Split String by Double Back Slashes in Bash

I am trying to split the string by double back slashes in bash and somehow it's not working.
My string is as follow:
abc\\xyz
My Code:
my_str='abc\\xyz'
IFS='\\' read -r -a arr <<< "$my_str"
echo "${#arr[#]}"
I am expecting that the output would be '2' as the length of the array would be 2.
However, I get 3 as a result and when I try to print the array values, I only get 'abc', and the second index remains empty. It seems like something is wrong with my code but I am unable to identify it.
Can anyone please help to resolve the issue?
If there are no spaces in your string you could use bash pattern substitutions to replace pairs of backslashes by a space and assign the result to an indexed array:
$ my_str='abc\\xyz\uvw\\rst\\\012\\\\345'
$ declare -a arr=( ${my_str//\\\\/ } )
$ echo "${#arr[#]}"
5
$ printf '%s\n' "${arr[#]}"
abc
xyz\uvw
rst
\012
345
Perhaps you could try to replace the backslashes on the string fist as showcased in this previous question. However, this would be inefficient for very large strings.
A slightly different take -
$: my_str='abc\\123\\ghi\\\012\\jkl\\foo bar\\\\xyz'
$: IFS='|' read -r -a arr <<< "${my_str//\\\\/\|}"
$: printf "[%s]\n" "${arr[#]}"
[abc]
[123]
[ghi]
[\012]
[jkl]
[foo bar]
[]
[xyz]

Creating and populating dynamically named arrays in bash

I am creating dynamic arrays which all have different letters in their name. For the purpose of this question, my initial array of letters has been set at a fixed length. However, in my final implementation this letter array will be any length.
For each letter, I construct a string
I declare a new array with that string, making use of eval to evaluate the variable's value within the declare command.
I add some values to the array, again using eval to evaluate any variable values.
Here is the code:
declare -a LETTER_ARRAY=( "A" "B" "C" "D" "E" )
for i in "${LETTER_ARRAY[#]}"
do
name_string="apple${i}"
color0="red"
color1="green"
eval "declare -a ${name_string}_array"
eval "${name_string}_array[0]=$color0"
eval "${name_string}_array[1]=$color1"
done
So, how can I iterate through these dynamic arrays and echo what is in them? I have tried the following:
for i in "${LETTER_ARRAY[#]}"
do
eval "array_name='apple${i}_array'"
echo ${array_name[0]}
done
This has not worked for me. I can confirm that my dynamic arrays were successfully created and populated, as when I echo out a value manually, I get a result:
echo ${appleA_array[0]}
red
A perfect place to use a namereference:
letter_array=(A B C D E)
for i in "${letter_array[#]}"; do
declare -n var="apple${i}_array"
var[0]=red
var[1]=green
done
declare -p appleA_array
would output:
declare -a appleA_array=([0]="red" [1]="green")
how can I iterate through these dynamic arrays and echo what is in them?
With the above:
for i in "${letter_array[#]}"; do
declare -n var="apple${i}_array"
printf "%s\n" "${var[0]}"
done
Notes:
Do not use eval. Eval is evil.
Do not use upper case variables, by convention they are used for exported variables, like COLUMNS, PWD, UID, EUID, LINES. Use lower case variables in your scripts.
Check your scripts with http://shellcheck.net for most common mistakes
But if you are creating a 2d array, then an associative array might be better:
declare -A apple_arrays
letter_array=(A B C D E)
for i in "${letter_array[#]}"; do
apple_arrays[$i,0]=red
apple_arrays[$i,1]=green
done
for i in "${letter_array[#]}"; do
printf "one=%s two=%s\n" "${apple_arrays[$i,0]}" "${apple_arrays[$i,1]}"
done
how can I iterate through these dynamic arrays
echo ${array_name[0]} does not work because array_name is not the name of an array; $array_name is. Therefore, eval "echo \"\${${array_name}[0]}\"" would to the trick.
However, I'd recommend namerefs.
By The way: declare works without eval and is more reliable that way.
#! /usr/bin/env bash
letters=({A..E})
for i in "${letters[#]}"; do
declare -a "apple${i}_array=(red green)"
done
for i in "${letters[#]}"; do
declare -n array="apple${i}_array"
# now you can use `array` as if it was `appleA_array`, `appleB_array`, ...
echo "${array[0]}"
done
Your first line is not bash syntax. If I try the line
declare -a LETTER_ARRAY = [ "A" "B" "C" "D" "E" ]
I get:
bash: declare: `=': not a valid identifier
bash: declare: `[': not a valid identifier
bash: declare: `]': not a valid identifier
I think, you get similar error messages, but you ignored them
More errors:
Forgotten do
case mismatch: suffix on definition: _array, and for output: _ARRAY
Use always double quotes when using [#]
One correct syntax is:
declare -a LETTER_ARRAY=( "A" "B" "C" "D" "E" )
for i in "${LETTER_ARRAY[#]}"
do
name_string="apple${i}"
color0="red"
color1="green"
eval "declare -a ${name_string}_array"
echo "${name_string}_array[0]=$color0"
eval "${name_string}_array[0]=$color0"
eval "${name_string}_array[1]=$color1"
done
echo ${appleA_array[0]}
Your eval "array_name='AZ${i}_ARRAY'" makes array_name a scalar, not an array. Arrays in bash are usually created like this
arr=( your elements go here )
If you want to assign one array to another, you have to interpolate the elements between those parenthesis, for instance:
arr=( ${other_array[#]} )
Since you are using bash, this would perform word splitting the elements of other_array, if they contain spaces. Hence you would usually write it for the safe side as
arr=( "${other_array[#]}" )
Hence, for your case, you could do a
eval "array_name=( \${AZ${i}_ARRAY[#]} )"
This causes an array array_name to be created, with the elements of the respective AZi_ARRAY.
I omitted here for simplicity the prevention against word splitting, because in your example, the array elements contain single words only.

How to iterate over values with space characters?

I have the following:
directories="directory_1 directory_2"
for k in ${directories}; do
echo "${k}"
done;
The problem is that if I add a directory with a blank space in its name, e.g. directory 3, this fails as it is treated as two strings.
Use an array:
A=(
foo
"bar a"
blub
)
for k in "${A[#]}"; do
echo "$k"
done
prints:
foo
bar a
blub
You can read more about arrays in the manual page man bash (search for arrays with "/ Arrays" and use n to move to the next site. h lists more commands) or in the Beginners Guide.

Echo $variable$counter in a "for" loop BASH

n=1
test=1000
test1=aaa
I'm trying:
echo $test$n
to get
aaa
But I get
10001
I'm trying to use it that way because I have variables: lignePortTCP1,lignePortTCP2,lignePortTCP1, ETC in a for loop like this:
declare -i cpt3
cpt3=0
for ((i = 1; i <= cpt; i++)); do
cpt3=cpt3+1
echo "Port/Protocole : $lignePortTCP$cpt3 - Nom du Service : $ligneServiceTCP$cpt3"
done
Given the assigned variables
n=1
test1=aaa
...and you want to print aaa given the values of test and n, then put the name you want to expand in its own variable, and expand that with the ! operator, like so:
varname="test$n"
echo "${!varname}"
This is explicitly discussed in BashFAQ #6.
That said, variable indirection is not a particularly good practice -- usually, you can do better using arrays, whether associative or otherwise.
For instance:
test=( aaa bbb ccc )
n=0
echo "${test[n]}"
...for values not starting at 0:
test=( [1]=aaa [2]=bbb [3]=ccc )
n=1
echo "${test[n]}"
If you want to subtract the values of test and n, wrap the computation in $(( ... )) and use
the - operator:
$((test-n))
You could also use eval, though it's probably not better than the technique provided by Charles Duffy.
$ n=1
$ test=1000
$ test1=aaa
$ eval echo \${test$n}
aaa
One way would be to use ${!}, but you have to store the combined name in its own variable for that to work:
var=test$n
echo "${!var}"
If you have control over how the variables get assigned in the first place, it would be better to use an array. Instead of lignePortTCP1, lignePortTCP2, etc., you would assign the values to lignePortTCP[0], lignePortTCP[1], etc. and then retrieve them with ${lignePort[$n]}.

Unable to set second to last command line argument to variable

Regardless of the number of arguments passed to my script, I would like for the second to the last argument to always represent a specific variable in my code.
Executing the program I'd type something like this:
sh myprogram.sh -a arg_a -b arg_b special specific
test=("${3}")
echo $test
The results will show 'special'. So using that same idea if I try this (since I won't know that number of arguments):
secondToLastArg=$(($#-1))
echo $secondToLastArg
The results will show '3'. How do I dynamically assign the second to last argument?
You need a bit of math to get the number you want ($(($#-1))), then use indirection (${!n}) to get the actual argument.
$ set -- a b c
$ echo $#
a b c
$ n=$(($#-1))
$ echo $n
2
$ echo ${!n}
b
$
Indirection (${!n}) tells bash to use the value of n as the name of the variable to use ($2, in this case).
You can use $# as array & array chopping methods:
echo ${#:$(($#-1)):1}
It means, use 1 element starting from $(($#-1))...
If some old versions of shells do not support ${array:start:length} syntax but support only ${array:start} syntax, use below hack:
echo ${#:$(($#-1))} | { read x y ; echo $x; } # OR
read x unused <<< `echo ${#:$(($#-1))}`

Resources