How to store variables from loop to a file - arrays

I am trying to store the variables $d, $tf_name, $db_orig created in the following loop to a file.I want to end up with a tab separated MY_FILE.txt containing the following fields $d, $tf_name, $db_orig and each iteration of this set of variables to be stored in a new line in the file MY_FILE.txt.
MY_ARRAY=()
for d in */
do
IN=$d
folderIN=(${IN//_/ })
tf_name=${folderIN[-1]%/*}
db_orig=${folderIN[-2]%/*};
ENTRY="$d\t$tf\t$id\t$db_orig\n"
MY_ARRAY+=$ENTRY
done
$MY_ARRAY > MY_FILE.txt
It doesn't recognise \t and \n as TAB and NEWLINE respectively. It stores all the values next to each other in the same line without TAB, in the array MY_ARRAY.
Any help?

Yes, this happens because $MY_ARRAY > MY_FILE.txt is not a valid command.
You need to print your array to the file.
And in order to print it correctly you need either to use
echo -e "${MY_ARRAY[#]}" >file or printf
By man echo
echo -e : enable interpretation of backslash escapes
Moreover, if you need to store the $ENTRY to your array you need to do it like this:
MY_ARRAY+=("$ENTRY")
In any case, you can do it without the need of an array. You can just apply += in the ENTRY : ENTRY+="$d\t$tf\t$id\t$db_orig\n"
Test:
$ e+="a\tb\tc\td\n"
$ e+="aa\tbb\tcc\tdd\n"
$ e+="aaa\tbbb\tccc\tddd\n"
$ echo -e "$e"
a b c d
aa bb cc dd
aaa bbb ccc ddd
# Test with array
$ e="a\tb\tc\td\n" && myar+=("$e")
$ e="aa\tbb\tcc\tdd\n" && myar+=("$e")
$ e="aaa\tbbb\tccc\tddd\n" && myar+=("$e")
$ echo -e "${myar[#]}"
a b c d
aa bb cc dd
aaa bbb ccc ddd
#Alternative array printing
$ for i in "${myar[#]}";do echo -en "$i";done
a b c d
aa bb cc dd

Related

Bash - get empty array in return on macOS

With regards to this question (Put line seperated grep result into array), when I use
echo v1.33.4 | arr=($(egrep -o '[0-9]{1,3}'))
with GNU bash, version 5.0.2(1)-release (x86_64-apple-darwin18.2.0) on macOS
I get have an empty array arr in return for
echo "($arr)"
()
then the expected output
1
33
4
What do I do wrong here?
It wouldn't work with the syntax you have. You are not populating the array with the result of grep. You are not handling the string passed over the pipe and populating an empty array at the received end of the pipe.
Perhaps you were intending to do
array=($(echo v1.33.4 | egrep -o '[0-9]{1,3}'))
Notice how the echo of the string is passed over to the standard input of egrep which was missing in your attempt.
But as in the linked answer, using mapfile would be the best option here because with the above approach if the search results contains words containing spaces, they would be stored in separated indices in the array rather than in a single one.
mapfile -t array < <(echo "v1.33.4" | egrep -o '[0-9]{1,3}')
printf '%s\n' "${array[#]}"
Notice the array expansion in bash takes the syntax of "${array[#]}" and not just a simple "${array}" expansion.
Messed with it a bit and this seems to work:
$ arr=$(echo "v1.33.4" | egrep -o '[0-9]{1,3}')
$ echo $arr
1 33 4
$ echo "($arr)"
(1
33
4)

How to store the list of subdirectories into an array and access them by their index in Bash?

Assume That we have a directory name "A" with 4 sub directories(aa,bb,cc,dd), some of the sub directories also have sub directories, so assume a schematic like below:
A
aa
aaa
bb
bbb
bbbb
cc
dd
I tried to list the sub directories(aa,bb,cc,dd) in an array and then use them in my script by their array number.
I used the script below for copying dd to parent directory:
while IFS= read -d '' file; do
A+=( "$file" )
done < <(find . -type d -print0 | LC_ALL=C sort -z)
cp -r `pwd`/${A[4]}" `pwd`/..
But the problem is that the script make an array of all of the sub-directories, [aa aaa bb bbb bbbb cc dd]
so ${a[4]} = bbb and not dd.
Any idea how to fix it?
You can restrict find to just look at the top directory, with the maxdepth option:
find . -type d -print0 -maxdepth 1 | LC_ALL=C sort -z
You can achieve the same thing in a simpler way using a glob:
dirs=(*/) # store all top level directories into the dirs array
dirs=("${dirs[#]%/}") # strip trailing / from each element of the array
and then
cp -r "$PWD/${dirs[4]}" "$PWD/.."
Double quotes are needed to prevent word splitting and globbing
pwd in backquotes can simply be written as $PWD, which doesn't need to create a subshell

Clarification on bash arrays: loaded from output of function vs. loaded from a loop

I've seen two different behaviors in bash arrays that I am having trouble understanding the underlying nature of:
The first instance, is whenever I capture multiple lines of output to a variable. I'm able to echo all of that data by echoing the name of the array:
[bennett#pc foo]$ foo=`cat filea.c`
[bennett#pc foo]$ echo $foo
asfed asdf asdf asd fasd fas dfs dfsd f sd d ddd
[bennett#pc foo]$ echo "$foo"
asfed
asdf
asdf
asd
fasd
fas
dfs
dfsd
f
sd
d
ddd
But when I then load another array with the same data using a for loop, the new array must be iterated over in order to echo all of the output, as echoing the name seems to output only the first element:
[bennett#pc foo]$ for i in $foo
> do
> otherarray+=( $i )
> done
[bennett#pc foo]$ echo $otherarray
asfed
[bennett#pc foo]$ for i in ${otherarray[#]}
> do
> echo "$i"
> done
asfed
asdf
asdf
asd
fasd
fas
dfs
dfsd
f
sd
d
ddd
What is going on behind the scenes here? Are these two different "types" of arrays?
In your first example, foo=$(cat file), you now have the foo variable holding a string, the contents of the file. The difference between echo $foo and echo "$foo" is word splitting -- see the list of shell expansions: https://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions
In the second example, you are using word splitting to iterate over the words of the contents of the variable, and storing those words into an array.
When you echo $arrayname you only get the first element of the array.
When you do not quote the array expansion -- for i in ${otherarray[#]} -- again, you are subjecting your data to word splitting.
An example:
$ cat file
one two
three four
# using the bash builtin for `cat file`
$ foo=$(< file)
$ echo $foo
one two three four
$ echo "$foo"
one two
three four
now, let's work with an array
$ for word in $foo; do
words+=($word)
done
$ echo $words
one
# without quotes
$ printf "%s\n" ${words[#]}
one
two
three
four
# with quotes
$ printf "%s\n" "${words[#]}"
one
two
three
four
We can see that each element is a word from the file, not a line.
Let's use the builtin mapfile command
$ mapfile -t words < file
# without quotes
$ printf "%s\n" ${words[#]}
one
two
three
four
# with quotes
$ printf "%s\n" "${words[#]}"
one two
three four
I suspect that's what you want, to store each line of the file as an array element.
Another way to iterate over the lines of a file is a while (not for) loop:
while IFS= read -r line; do # use `IFS=` to avoid trimming whitespace
# and `read -r` to avoid messing with backslashes
array+=("$line") # use quotes
done < file
tl;dr
always quote your "$variables" except when you know exactly when to leave the quotes off.
use one of these constructs to iterate over the lines of a file
mapfile -t array < file
while IFS= read -r line; do ...; done < file

bash while array containing space

I got the following code. I would like to make cc dd ee ff as array [2]
keyvariable="aa bb cc dd ee ff"
while read -a line;
do
a=$(echo "${line[0]}")
b=$(echo "${line[1]}")
c=$(echo "${line[2]}")
done <<< "$keyvariable"
echo "$a $b $c"
current output:
aa bb cc
I would like to have the following output, where aa is [0] bb is [1] and cc dd ee is [2]
aa bb cc dd ee
You don't need the while loop here at all.
You don't want to use read with the -a switch at all here. Instead you want:
read -r a b c <<< "$keyvariable"
In this case, read will split the (first line of the) expansion of the variable keyvariable on the spaces, but only for the first and second fields (these will go in variables a and b) and the remaining part will go in c. The -r switch is used in case you have backslashes in your string; without this, backslashes would be treated as an escape character.
gniourf_gniourf's answer is absolutely correct, however if you don't know how many fields you are going to have or need to select your "prefix" fields based on some other criteria then using an array and Substring Expansion (which is a bad name for this usage of it but that's what it is called) can let you do that.
$ keyvariable="aa bb cc dd ee ff"
$ read -a line <<<"$keyvariable"
$ a=${line[0]}
$ b=${line[1]}
$ c=${line[#]:2}
$ echo "$a"
aa
$ echo "$b"
bb
$ echo "$c"
cc dd ee ff
Also note the lack of $(echo ...) on the assignment lines. You don't need it.
Just do
a=( $keyvariable )
and you have array a with your values, so your example would be
keyvariable="aa bb cc dd ee ff"
line=( $keyvariable ) # of course you can do it in the same line
a="${line[0]}"
b="${line[1]}"
n=$(( ${#line[#]} - 1))
unset line[$n]
echo "$a $b $c"

Bash script to loop through file name to delete at specific index

I have a ton of files that are named like this:
nn - xxxxxxxxxxxxxx-OOO.ext
Where nn is always a two digit number and xxxxx is a variable length of text. (The suffix of -OOO is static throughout all of the files). What should be in the loop to rename the files to:
xxxxxxxxxxxxxx.ext
Thus removing the nn -(always the first 5 characters) and the -OOO.
You can do that with two substring operations:
$ name="nn - xxxx x xx xx xxxxx-OOO.ext"
$ part1=${name:5} # substring starting at position 5
$ part2=${part1%-OOO.ext} # remove `-OOO.ext` at the end of $part1
$ final="$part2".ext
$ echo $final
xxxx x xx xx xxxxx.ext
$ mv "$name" "$final"
echo $file_name | sed "s/.*-\s*\(.*\)-.*/\1.ext/" will give you the "xxxxxxx.ext" as you asked for in the OP.

Resources