Saving directory content to an array (bash) [duplicate] - arrays

This question already has answers here:
How do you store a list of directories into an array in Bash (and then print them out)?
(4 answers)
Closed 7 years ago.
I need to save content of two directories in an array to compare them later. Thats the solution i write:
DirContent()
{
#past '$1' directorys to 'directorys'
local DIRECTORYS=`ls -l --time-style="long-iso" $1 | egrep '^d' | awk '{print $8}'`
local CONTENT
local i
for DIR in $DIRECTORYS
do
i=+1
CONTENT[i]=${DIR}
done
echo $CONTENT
}
Then when I try to print this array I get empty output. Both directories are not empty. Please tell me what am I doing wrong here.
Thanks, Siery.

The core of this question is answered in the one I marked as a duplicate. Here are a few more pointers:
All uppercase variable names are discouraged as they are more likely to clash with environment variables.
You assign to DIRECTORYS (should probably be "directories") the output of a complicated command, which suffers from a few deficiencies:
Instead of backticks as in var=`command`, the syntax var=$(command) is preferred.
egrep is deprecated and grep -E is preferred.
The grep and awk commands could be combined to awk /^d/ '{ print $8 }'.
There are better ways to get directories, for example find, but the output of find shouldn't be parsed either.
You shouldn't process the output of ls programmatically: filenames can contain spaces, newlines, other special characters...
DIRECTORYS is now just one long string, and you rely on word splitting to iterate over it. Again, spaces in filenames will trip you up.
DIR isn't declared local.
To increase i, you'd use (( ++i )).
CONTENT[i]=${DIR} is actually okay: the i is automatically expanded here and doesn't have to be prepended by a $. Normally you'd want to quote your variables like "$dir", but in this case we happen to know that it won't be split any further as it already is the result of word splitting.
Array indices start at zero and you're skipping zero. You should increase the counter after the assignment.
Instead of using a counter, you can just append to an array with content+=("$dir").
To print the contents of an array, you'd use echo "${CONTENT[#]}".
But really, what you should do instead of all this: a call DirContent some_directory is equivalent to echo some_directory/*/, and if you want that in an array, you'd just use
arr=(some_directory/*/)
instead of the whole function – this even works for weird filenames. And is much, much shorter.
If you have hidden directories (names starts with .), you can use shopt -s dotglob to include them as well.

You can try
for((i=0;i<${#CONTENT[*]};i++))
do
echo ${CONTENT[$i]}
done
instead of echo $CONTENT
Also these change are required
((i=+1))
CONTENT[$i]=${DIR}
in your above code

Related

Bash: Store sed result into array?

How to fix the following code so that it can store the result of sed, which will replace the _
with -?
My code:
names=()
for entry_ in $foo
do
names+=($entry_ | sed -e "s/_/-/g")
done
echo names
You don't need sed for this, you can use bash's built-in parameter expansion + substitution capability to replace all _ characters with -: ${var//_/-}. You can even use it to do this for the entire list of elements in a single operation, but how you do it depends on what the source variable, foo, actually is.
If foo is an array (the much better way to do things), you can combine [#] ("get me all elements of the array") with the substitution:
names=( "${foo[#]//_/-}" )
If foo is a plain string, and you need to use word splitting to break it into elements for the array, you can do essentially the same thing without the [#] ('cause it's not an array) or the double-quotes (which prevent word splitting):
names=( ${foo//_/-} )
Note: I recommend avoiding word splitting if possible -- it often does something close to what you want, but almost never exactly what you want.
P.s. I third the recommendation of shellcheck. Among other things, it'll flag anything involving word splitting as a probable mistake.
This should be enough to get you there.
names=()
names+=$(echo "hello_world" | sed -e "s/_/-/g")
echo $names
Note that you need $ before echoing your variable.
Also. Look into installing shellcheck for your code editor and it will help you catch sneaky bugs and build better shell programming practices.

Why does "echo $array" print all members of the array in this specific case instead of only the first member like in any other case?

I have encountered a very curious problem, while trying to learn bash.
Usually trying to print an echo by simply parsing the variable name like this only outputs the first member Hello.
#!/bin/bash
declare -a test
test[0]="Hello"
test[1]="World"
echo $test # Only prints "Hello"
BUT, for some reason this piece of code prints out ALL members of the given array.
#!/bin/bash
declare -a files
counter=0
for file in "./*"
do
files[$counter]=$file
let $((counter++))
done
echo $files # prints "./file1 ./file2 ./file3" and so on
And I can't seem to wrap my head around it on why it outputs the whole array instead of only the first member. I think it has something to do with my usage of the foreach-loop, but I was unable to find any concrete answer. It's driving me crazy!
Please send help!
When you quoted the pattern, you only created a single entry in your array:
$ declare -p files
declare -a files=([0]="./*")
If you had quoted the parameter expansion, you would see
$ echo "$files"
./*
Without the quotes, the expansion is subject to pathname generation, so echo receives multiple arguments, each of which is printed.
To build the array you expected, drop the quotes around the pattern. The results of pathname generation are not subject to further word-splitting (or recursive pathname generation), so no quotes would be needed.
for file in ./*
do
...
done

bash store output of command in array

I'm trying to find if the output of the following command, stores just one file in the array array_a
array_a = $(find /path/dir1 -maxdepth 1 -name file_orders?.csv)
echo $array_a
/path/dir1/file_orders1.csv /path/dir1/file_orders2.csv
echo ${#array_a[#]}
1
So it tell's me there's just one element, but obviously there are 2.
If I type echo ${array_a[0]} it doesn't return me anything. It's like, the variable array_a isn't an array at all. How can i force it to store the elements in array?
You are lacking the parentheses which define an array. But the fundamental problem is that running find inside backticks will split on whitespace, so if any matching file could contain a space, it will produce more than one element in the resulting array.
With -maxdepth 1 anyway, just use the shell's globbing facilities instead; you don't need find at all.
array_a=(/path/dir1/file_orders?.csv)
Also pay attention to quotes when using the array.
echo "${array_a[#]}"
Without the quotes, the whitespace splitting will happen again.

How do you assign lines of output containing spaces to a bash array [duplicate]

This question already has answers here:
Capturing output of find . -print0 into a bash array
(13 answers)
Closed 7 years ago.
I would like to take the output of something that returns lines of elements possibly containing spaces to a bash array, with each line getting its own array element.
So for example:
find . -name \*.jpg
... may return a list of filenames. I want each filename to be assigned to an array. The simple solution doesn't work in general, because if there are spaces in filenames, the words get their own array element.
For example, start with this list of files in a directory:
FILE1.jpg
FILE2.jpg
FILE WITH SPACES.jpg
Try:
FILES=( $(find . -name \*.jpg) )
And you get (<> added for emphasis of individual elements):
$ for f in "${FILES[#]}"; do echo "<$f>"; done
<./FILE>
<WITH>
<SPACES.jpg>
<./FILE1.jpg>
<./FILE2.jpg>
This is not likely what you want.
How do you assign lines to array elements regardless of the lines containing spaces?
Set IFS before making the assignment. This allows bash to ignore the spaces by using only "\n" as the delimiter:
IFS=$'\n'
FILES=( $(find . -name \*.jpg) )
Now you get the result:
for f in "${FILES[#]}"; do echo "<$f>"; done
<./FILE WITH SPACES.jpg>
<./FILE1.jpg>
<./FILE2.jpg>
Note that how you access the array is important as well. This is covered in a similar question: BASH array with spaces in elements

How to read lines from a file into an array?

I'm trying to read in a file as an array of lines and then iterate over it with zsh. The code I've got works most of the time, except if the input file contains certain characters (such as brackets). Here's a snippet of it:
#!/bin/zsh
LIST=$(cat /path/to/some/file.txt)
SIZE=${${(f)LIST}[(I)${${(f)LIST}[-1]}]}
POS=${${(f)LIST}[(I)${${(f)LIST}[-1]}]}
while [[ $POS -le $SIZE ]] ; do
ITEM=${${(f)LIST}[$POS]}
# Do stuff
((POS=POS+1))
done
What would I need to change to make it work properly?
I know it's been a lot of time since the question was answered but I think it's worth posting a simpler answer (which doesn't require the zsh/mapfile external module):
#!/bin/zsh
for line in "${(#f)"$(</path/to/some/file.txt)"}"
{
// do something with each $line
}
#!/bin/zsh
zmodload zsh/mapfile
FNAME=/path/to/some/file.txt
FLINES=( "${(f)mapfile[$FNAME]}" )
LIST="${mapfile[$FNAME]}" # Not required unless stuff uses it
integer POS=1 # Not required unless stuff uses it
integer SIZE=$#FLINES # Number of lines, not required unless stuff uses it
for ITEM in $FLINES
# Do stuff
(( POS++ ))
done
You have some strange things in your code:
Why are you splitting LIST each time instead of making it an array variable? It is just a waste of CPU time.
Why don’t you use for ITEM in ${(f)LIST}?
There is a possibility to directly ask zsh about array length: $#ARRAY. No need in determining the index of the last occurrence of the last element.
POS gets the same value as SIZE in your code. Hence it will iterate only once.
Brackets are problems likely because of 3.: (I) is matching against a pattern. Do read documentation.
Let's say, for the purpose of example, that file.txt contains the following text:
one
two
three
The solution depends on whether or not you'd like to elide the empty lines in file.txt:
Creating an array lines from file file.txt, eliding empty lines:
typeset -a lines=("${(f)"$(<file.txt)"}")
print ${#lines}
Expected output:
3
Creating an array lines from file file.txt, without eliding empty lines:
typeset -a lines=("${(#f)"$(<file.txt)"}")
print ${#lines}
Expected output:
5
In the end, the difference in the resulting array is a result of whether or not the parameter expansion flag (#) is provided during brace expansion.
while read -r line;
do ARRAY+=("$line");
done < file.txt

Resources