Creating an array from substrings in Bash not working [duplicate] - arrays

This question already has answers here:
Split string into array using multi-character delimiter
(5 answers)
Closed 2 years ago.
Why are the last two array elements being truncated?
I'm trying to generate an array from a string in bash, using a \n as the delaminating character. Using command substitution and tr I was close to what I wanted, now I'm getting strange output.
Script file below
input string: abcd\nefgh\nijkl\nmnop
#!/bin/bash
oper () {
local fct=$1
local s=$2
IFS='\n' read -ra sArr <<< "$s"
for item in ${sArr[#]}
do
printf " %s\n" $(echo $item | rev)
done
}
echo "original string: $2"
oper $1 $2
printf "mirror string: "
exit 0
Here's the output
original string: abcd\nefgh\nijkl\nmnop
dcba
hgfe
lkji
m
po

Why are the last two array elements being truncated?
Because any character in IFS causes splitting into words, not both. Seeting IFS='\n' splits the input string on \ or n. Because mnop has an n in it, it is splitted to m and op.
First change the sequence of \n character to something that is a single character and uniq - then you may use IFS=.. read. For example:
IFS='!' read -r -a sarr < <(sed 's/\\n/!/g' <<<"$s")
Remember to check you script with http://shellcheck.net to correct quoting problems.

Related

Bash echo of array element not showing up [duplicate]

This question already has answers here:
Bash: Split string into character array
(20 answers)
Closed 6 months ago.
I am reading a file that basically contains a list of binary data (fixed at 8 bits wide per line):
01011100
11110001
...
For each line that is read, I need to "remap" the bits in chunks of 4 bits to specific positions. So for example in the 1st line above, bits 1100 and 0101 will each be remapped that will follow this formula: bit 0 goes to bit 3 position, bit 1 to 2, bit 3 to 1, and lastly bit 2 to 0.
To do this, I coded as follows:
function remap {
echo "Remapper";
IFS=
read -ra din <<< $1;
echo ${#din};
echo ${din[1]};
## above line is just displaying blank as seen in below result
echo ${din[*]};
## do actual remapping here
};
for line in `cat $infile`;
do
data0=${line:0:4};
data1=${line:4:4};
echo "Read line";
echo $data0;
echo $data1;
remap $data0;
remap $data1;
done
I don't know why I'm not seeing the echoed array element. This is the output from the 1st read line:
Read line
0101
1100
Remapper
4
0101
Remapper
4
1100
I haven't gotten to coding the actual remapping itself because I couldn't even verify that I'm able to properly split the $1 variable of remap() function into the din array.
Thank you in advance for the help!
Unlike other languages, setting IFS to empty string does not split a string
into each character array. Instead you can use fold -w1 to
add a newline after each character:
remap() {
echo "Remapper"
mapfile -t din < <(fold -w1 <<< "$1")
echo "${din[3]}${din[2]}${din[0]}${din[1]}"
}
As it will be inefficient to invoke fold command every time, it
may be better to say:
remap() {
echo "Remapper"
echo "${1:3:1}${1:2:1}${1:0:1}${1:1:1}"
}
As a side note, you don't need to append semicolon after each command.
There are a number of confusions here. The biggest is that read -ra din is not splitting the string into characters. read will split its input into words delimited by the characters in IFS; normally that's whitespace, but since you set IFS to the empty string, there are no delimiters and the string won't be split at all. Anyway, you don't want to split it based on delimiters, you want to split it into characters, so read is the wrong tool.
Another source of confusion is that ${#din} isn't the length of the array, it's the length (in characters) of the first element of the array. ${#din[#]} would get the number of elements in the array, and in this case it'd be 1. More generally, declare -p din would be a better way to see what din is and what it contains; here, it'd print something like declare -a din='([0]="0101")', showing that it's an array with a single four-character element, numbered 0.
What I'd do here is skip trying to split the characters into array elements entirely, and just index them as characters in $1 -- that is, ${1:0:1} will get the first character (character #0) from $1, ${1:1:1} will get the second (#1), etc. So to print the bits in the order third, first, second, fourth, you'd use:
echo "${1:2:1}${1:0:1}${1:1:1}${1:3:1}"
Other recommendations: It's best to double-quote variable expansions (like I did above) to prevent weird parsing problems. for var in $(cat somefile) is a fragile way to read lines from a file; while read var; do ... done <somefile is generally better. I'd recommend remap() { instead of the nonstandard function remap { syntax, and semicolons are redundant at the end of lines (well... with a few weird exceptions). shellcheck.net will point most of these out, and is a good tool to sanity-check your scripts for common mistakes.

Saving part of each line to an array in bash

I am trying to read from a file that is storing user names and addresses in the format of name:address on each new line and I wish to store only the addresses into an array. Is there any way to do this? My code currently looks like this:
while IFS=: read -r username address; do
array=${address}
done <userfile.txt
Which is only storing the address from the first line in the file and stopping.
You are almost right! You just need to append to the array using the += operator (append) which bash arrays provide.
declare -a myArray=()
while IFS=: read -r username address; do
myArray+=("$address")
done < userfile.txt
Doing the above should do the trick for you. Note that the parentheses are also critical here. array+=(something) appends a new element to the array, while array+=something just appends text to the first element of the array. Optionally later to print the array contents each in separate line use printf as
printf "%s\n" "${myArray[#]}"
You can use array+=($address) form of adding array element.
array=()
while IFS=: read -r username address; do
array+=("$address")
done < userfile.txt
echo ${array[#]}

Read array from file separated with commas and newlines

I have a file that has two different words per line, delimited by a comma and a line break. How can you read this file and store every word in an array? My code doesn't work because I think only works for "one line" array.
File Sample:
Each word is separated by a comma and a line break.
Dog,cat
shark,rabbit
mouse,bird
whale,dolphin
Desired input
"${array[0]}" = Dog
"${array[1]}" = cat
"${array[2]}" = shark
"${array[3]}" = rabbit
"${array[4]}" = mouse
"${array[5]}" = bird
"${array[6]}" = whale
"${array[7]}" = dolphin
My Code:
input=$(cat "/path/source_file")
IFS=',' read -r -a array <<< "$input"
IFS=$'\n,' read -d '' -ra array < file
The key is to use IFS to tell read to split the entire input) -d '') into array elements (-a; -r ensures unmodified reading) by both \n and , characters.
For simplicity, I've used file to represent your input file and used it directly as input to read via stdin (<).
If you do have a need to read the entire file into a shell variable first, the following form is slightly more efficient in Bash (but is not POSIX-compliant):
input=$(< "/path/source_file")
Input Format:
Read the inarr1 from line 1 with array elements separated by (,) comma.
Read the inarr2 from line 2 with array elements separated by (,) comma.
Read the input from the standard input stream
Output format:

Printing 2D array in C language using system ECHO command [duplicate]

This question already has answers here:
Using scalar variable or a pointer to print 2D array
(4 answers)
Closed 6 years ago.
How do I execute the system command “echo” in a C program to print out all the elements in a 2D array? I tried it, but it didn't work.
Input:
char myarray[1][10] = {"Hello", "World"};
system("echo $myarray");
Expected Output:
Hello World
Currently I do not get any output whatsoever.
Update 1 (constraints):
I cannot allocate more memory – I can only use the myarray variable.
I may use any other scalar or pointer variables.
You need to build the whole command in a single string before calling system. Something like:
char myarray[2][10] = {"Hello", "World"};
// ^ notice
char mycommand[42];
strcpy(mycommand, "echo \"");
strcat(mycommand, myarray[0]);
strcat(mycommand, " ");
strcat(mycommand, myarray[1]);
strcat(mycommand, "\"");
system(mycommand);
I'm assuming this is on a Linux machine, not Windows!
The system command echo takes as a parameter what you want it to print out. So you'd think that
echo $myArray
would print out
$myArray
but it doesn't. The $ means "Look up the following string in the list of environment variables and print that". And myArray isn't defined in your list of environment variables, so it prints out nothing. It definitely won't print out a variable inside your computer program!
If you changed the call to
system("echo $path");
you would get a long string printed out, listing a number of directories. That's your current path.
But none of that answers your problem. What you want to do is print out Hello World using the echo command. That means that you need to (effectively) call the system() function with the following argument:
system("echo Hello world");
The only way you can do that is to "construct" the string that you want it to print out, and then tell system() the complete string.
So you need something like the following:
#define NUMSTRINGS 2 // Note that there are 2 strings!
char myArray[NUMSTRINGS][10] = {"Hello", "world"};
char command[256] = "echo"; // Start command
int i;
for (i=0; i<NUMSTRINGS; ++i) {
strcat(command, " "); // Space separator
strcat(command, myArray[i]); // Next string
} // for
system(command);
This will not work, because myarray is a variable in C, and $myarray is an environment variable in the shell. They have no relation to one another--just as "Reading, England" and "Reading the news" have no relation.
If you want to pass variables to the shell, you can try setenv().

How to read and split comma separated file in a bash shell script?

I want to read a file line by line, split each line by comma (,) and store the result in an array. How to do this in a bash shell script?
Sample line in my comma separated file
123,2014-07-21 10:01:44,123|8119|769.00||456|S
This should be the output after splitting:
arr[0]=123 arr[1]=2014-07-21 10:01:44 arr[2]=123|8119|769.00||456|S
Use read -a to split each line read into array based from IFS.
while IFS=, read -ra arr; do
## Do something with ${arr0]}, ${arr[1]} and ${arr[2]}
...
done < file
If the third field can also contain commas, you can prevent it from being split by using finite non-array parameters:
while IFS=, read -r a b c; do
## Do something with $a, $b and $c
...
done < file
From help read:
Reads a single line from the standard input, or from file descriptor FD
if the -u option is supplied. The line is split into fields as with word
splitting, and the first word is assigned to the first NAME, the second
word to the second NAME, and so on, with any leftover words assigned to
the last NAME. Only the characters found in $IFS are recognized as word
delimiters.
-a array assign the words read to sequential indices of the array
variable ARRAY, starting at zero

Resources