concatenate variable into single variable separated by comma with for loop - linux

I want to add the values to a variable, separated by comma, using for loop.
First values should remain first and so on.
for ((i=0; i<${#MYARRAY[#]}; i++));
do
ALL=$ALL$MYARRAY$i,
done
echo $ALL
I expect the output
val1,val2,val3
but the actuel output is
val1,val2,val3,
How to avoid the comma after the last value?

Just add one of the three statements after your for loop:
ALL=${ALL%,}
ALL=${ALL::-1}
ALL=${ALL%?}

Another option is with the translate (tr) command. For example:
$ myarray=(val1 val2 val3 val4)
$ echo ${myarray[*]}
val1 val2 val3 val4
$ myarray=$(echo ${myarray[*]} | tr ' ' ,) # Replace space with ','
$ echo $myarray # Gives what you need
val1,val2,val3,val4

https://www.tldp.org/LDP/abs/html/string-manipulation.html is a good source. Insert the following line after the loop.
ALL=${ALL%,}

In this example, the first iteration does not put a comma in $ALL. In the following iteration, a comma is placed before the value. This way, there won't be any comma at the end of the output string.
MYARRAY=(val val val)
for (( i=0; i<${#MYARRAY[#]}; i++ ))
do
if [ $i == 0 ]
then
ALL=$ALL$MYARRAY$i
else
ALL=$ALL,$MYARRAY$i
fi
done
echo $ALL

This is exactly what the [*] construct is for:
myarray=(val1 val2 val3 val4)
oldIFS="$IFS"
IFS=','
echo "${myarray[*]}"
IFS="$oldIFS"
gives:
val1,val2,val3,val4
I am using lowercase myarray because uppercase should be reserved for system (bash) variables.
Note that "${myarray[*]}" must be inside double-quotes, otherwise you do not get the join magic. The elements are joined by the first character of IFS, which by default is a space.

Related

Bash remove substring in file from string

I've one string like this:
myString='value1|value57|value31|value21'
and I've a file, called values_to_remove.txt containing a list of values, one per line, in this way
values_to_remove.txt
value1
value31
In bash, how can I remove the values contained in "values_to_remove.txt" from the string, taking into account that the values are separated by pipe and of course if I remove a value I have to removee also the preceding and the following pipe if any.
I've achieved this in python and called the python script from bash, but I need to do this directly in bash with one line command, rather than small script, otherwise I can already use my little python script.
That's the python code
myString = 'value1|value2|value3|value4'
arrString = myString.split("|")
with open("myfile.txt", encoding="utf-8") as file:
for l in file:
if l in arrString:
arrString.remove(l)
myNewString = "|".join(arrString)
Note that: the values separeted by pipe can be anything string.
Thank you
You may use this awk:
awk -v str="$myString" 'BEGIN {
n = split(str, a, /\|/)
}
{
val[$1]
}
END {
for (i=1; i<=n; i++)
if (!(a[i] in val))
s = (s == "" ? "" : s "|") a[i]
print s
}' values_to_remove.txt
value57|value21
This awk first uses a split function to split input string on |
It stores all values to be removed in another array val
In the end block it loops through split array and builds a string if value is not found in to-be-removed array.
Here is a bash solution (The if statement is a runtime optimization to skip the repacement in case of no match, thanks #Inian):
for val in value1 value31; do
if [[ "$mystring" =~ \|$val|$val\| ]]; then
mystring=${mystring/$BASH_REMATCH/}
fi
done
This looks in pure bash for the first regular expression that matches either |value or value| and removes it. Note you can match both at the same times because then you will delete too many separators. If there is a chance there are no separators you need to use ? after each pipe (maybe just the second one is enough).
You can also avoid regular expressions and just attempt to delete both a prior and a posterior pipe:
for val in value1 value31; do
mystring=${mystring/|$val/};
mystring=${mystring/$val|/};
done
All of these can be written on one line if you really need to:
for val in value1 value31; do [[ "$mystring" =~ \|$val|$val\| ]]; mystring=${mystring/$BASH_REMATCH/}; done
A pure bash solution:
#!/usr/bin/env bash
# Define the location of the values-to-be-removed file
: ${PATH_TO_FILE:=${1:-"./values_to_remove.txt"}}
# Define the string we will be working with
: ${MY_STRING:=${2:-"value1|value57|value31|value21"}}
# Process all entries in PATH_TO_FILE, one by one
while read -r substring || [[ -n "$line" ]]; do
# Remove "substring|" from the beginning of MY_STRING
MY_STRING=${MY_STRING#${substring}|}
# Remove "|substring" from the rest of MY_STRING
MY_STRING=${MY_STRING//|${substring}}
done < "${PATH_TO_FILE}"
# Return the results
echo ${MY_STRING}
Why do we...
Use ${VAR_NAME:=${1:-"DEFAULT_VALUE"}} notation - To allow the user to customise script's inputs either via environment variables or script arguments. Basically, this notation says:
If VAR_NAME environment variable exists, then use it;
If VAR_NAME doesn't exist, then set VAR_NAME to the value of the first argument to the script;
If the first argument doesn't exist either, then set VAR_NAME to the DEFAULT_VALUE.
Use read -r substring || [[ -n "$line" ]] to read the file? – read allows us to read content of ./values_to_remove.txt file, line by line. The [[ -n "$line" ]] bit is there to catch the last line in the file if it doesn't end with a newline.
References:
Assign a default value in bash
Return default value in bash
Bash substring removal
Bash search and replace

Bash: repeat character a variable number of times

Per the questions and ruminations in:
https://unix.stackexchange.com/questions/188658/writing-a-character-n-times-using-the-printf-command
and
How can I repeat a character in bash?
I would like to learn how one might go about parameterizing the repeat value for a character/string. For example, the followings works spiffingly:
printf " ,\n%0.s" {1..5}
However, if I wanted to parameterize '5', say:
num=5
I cannot seem to get the expansion correct to make this work. For instance:
printf " ,\n%0.s" {1..$((num))}
fails.
Any thoughts/ideas would be most welcome - I reckon there's a way to do this without having to resort to perl or awk so just curious if poss.
Thanks!
You can use seq
num=20;
printf '\n%.0s' $(seq $num)
If you can build the command as a string -- with all the parameter expansion you want -- then you can evaluate it. This prints X num times:
num=10
eval $(echo printf '"X%0.s"' {1..$num})
A slighly different approach
$ repeat() {
local str=$1 n=$2 spaces
printf -v spaces "%*s" $n " " # create a string of spaces $n chars long
printf "%s" "${spaces// /$str}" # substitute each space with the requested string
}
$ repeat '!' 10
!!!!!!!!!! # <= no newline
$ repeat $' ,\n' 5
,
,
,
,
,

How to get value from command line using for loop

Following is the code for extracting input from command line into bash script:
input=(*);
for i in {1..5..1}
do
input[i]=$($i);
done;
My question is: how to get $1, $2, $3, $4 values from input command line, where command line code input is:
bash script.sh "abc.txt" "|" "20" "yyyy-MM-dd"
Note: Not using for i in "${#}"
#!/bin/bash
for ((i=$#-1;i>=0;i--)); do
echo "${BASH_ARGV[$i]}"
done
Example: ./script.sh a "foo bar" c
Output:
a
foo bar
c
I don't know what you have against for i in "$#"; do..., but you can certainly do it with shift, for example:
while [ -n "$1" ]; do
printf " '%s'\n" "$1"
shift
done
Output
$ bash script.sh "abc.txt" "|" "20" "yyyy-MM-dd"
'abc.txt'
'|'
'20'
'yyyy-MM-dd'
Personally, I don't see why you exclude for i in "$#"; do ... it is a valid way to iterate though the args that will preserve quoted whitespace. You can also use the array and C-style for loop as indicated in the other answers.
note: if you are going to use your input array, you should use input=("$#") instead of input=($*). Using the latter will not preserve quoted whitespace in your positional parameters. e.g.
input=("$#")
for ((i = 0; i < ${#input[#]}; i++)); do
printf " '%s'\n" "${input[i]}"
done
works fine, but if you use input=($*) with arguments line "a b", it will treat those as two separate arguments.
If I'm correctly understanding what you're trying to do, you can write:
input=("$#")
to copy the positional parameters into an array named input.
If you specifically want only the first five positional parameters, you can write:
input=("${#:1:5}")
Edited to add: Or are you asking, given a variable i that contains the integer 2, how you can get $2? If that's your question, then — you can use indirect expansion, where Bash retrieves the value of a variable, then uses that value as the name of the variable to substitute. Indirect expansion uses the ! character:
i=2
input[i]="${!i}" # same as input[2]="$2"
This is almost always a bad idea, though. You should rethink what you're doing.

edit ASCII value of a character in bash

I am trying to update the ASCII value of each character of a string array in bash on which I want to add 2 to the existing character ASCII value.
Example:
declare -a x =("j" "a" "f" "a" "r")
I want to update the ASCII value incrementing the existing by 2 , such "j" will become "l"
I can't find anything dealing with the ASCII value beyond
print f '%d' "'$char"
Can anyone help me please?
And also when I try to copy an array into another it doesn't work
note that I am using
declare -a temp=("${x[#]}")
What is wrong with it?
You can turn an integer into a char by first using printf to turn it into an octal escape sequence (like \123) and then using that a printf format string to produce the character:
#!/bin/bash
char="j"
printf -v num %d "'$char"
(( num += 2 ))
printf -v newchar \\$(printf '%03o' "$num")
echo "$newchar"
This only works for ASCII.
It seems tr can help you here:
y=($(echo ${x[#]} | tr a-z c-zab))
tr maps characters from one set to another. In this example, from the set of a b c ... z, it maps to c d e ... z a b. So, you're effectively "rotating" the characters. This principle is used by the ROT13 cipher.

string alignment in perl / match alignment

I have two strings $dna1 and $dna2. Print the two strings as concatenated, and then print the second string lined up over its copy at the end of the concatenated strings. For example, if the input
strings are AAAA and TTTT, print:
AAAATTTT
TTTT
this is a self exercise question .. not a homework ,
i tried using index
#!/usr/bin/perl -w
$a ='AAAAAAAAAATTTTTTTTT';
$b ='TTTTTTTTTT';
print $a,"\n";
print ''x index($a,$b),$b,"\n";
but it is not working as needed .help please
Start by checking what index($a,$b) is returning... Perhaps you should pick a $b that's actually in $a!
Then realise that concatenating 10 instances of an empty string is an empty string, not 10 spaces.
This is a fun little exercise. I did this:
perl -lwe'$a="AAAA"; $b="TTTT"; $c = $a.$b; $i = index($c,$b) + length($b);
print $c; printf "%${i}s\n", $b;'
AAAAAAATTTT
TTTT
Note that generally speaking, using the variable names $a through $c is a bad idea, and only acceptable here because it is a one-liner. $a and $b are also reserved variable names used with sort.

Resources