Store all subdirectories of /Volumes in an array (BASH) - arrays

I need a script that will get all of the directories within the /Volumes directory on a Mac and store them in an array. The problem that I am running into is that it is very common for there to be a space in a directory name, and that really messes things up.
Here is what I've got so far:
LOCATION="/Volumes"
COUNTER=0
cd $LOCATION
OIFS=$IFS
IFS=$'\n'
for folder in *; do
[ -d "$folder" ] || continue
(( DRIVES[$COUNTER] = ${folder} ))
(( COUNTER = COUNTER + 1 ))
done
IFS=$OIFS
Here is the error that I am getting:
./getDrives.sh: line 17: DRIVES[0] = Macintosh HD : syntax error in expression (error token is "HD ")

I guess the simplest is just:
array=( /Volumes/*/ )
Notes:
use this with nullglob or failglob set
if you also want hidden directories (but not . nor ..), set dotglob
if you want all the directories and subdirectories (recursively), set globstar and use
array=( /Volumes/**/ )
instead.
When I say set nullglob or failglob or dotglob or globstar I mean the shell options, that can be set with, e.g.:
shopt -s nullglob
and unset with, e.g.:
shopt -u nullglob
More about these in The Shopt Builtin section of the Bash Reference Manual.
To answer your comment: you only want the basename of the directories, not the full path? easy, just do
cd /Volumes
array=( */ )
That's all. In fact, I'm suggesting you replace 6 lines of inefficient code with just one, much more efficient, line.
More generally, if you don't want to cd into /Volumes, you can easily get rid of the leading /Volumes/ like so
array=( /Volumes/*/ )
array=( "${array[#]/#\/Volumes\//}" )
Or, even better, put the leading /Volumes/ in a variable and proceed as:
location="/Volumes/"
array=( "$location"* )
array=( "${array[#]/#"$location"/}" )

cd /Volumes
cnt=0
for d in *; do
[ -d "$d" ] || continue
drv[$cnt]="$d"
((++cnt))
done
for d in "${drv[#]}"; do
echo "$d"
done

Related

Create files inside folders with nested loops and arrays

I am trying to create 2 folders and some files inside them. But it can't create more than the 1st folder and the 1st file. Code says it can't create the 1st folder since it is exist. Don't even try to create the rest of the files and folders.
Here is what I tried
#!/bin/bash
declare -a arrRel=(rel20 rel21)
declare -a arrVar=(pt_el pt_mu)
declare -a arrVarTitle=("electron p_T" "muon p_T")
for i in "${arrRel[#]}"
do
mkdir "${arrRel[$i]}"
cd "${arrRel[$i]}"
for j in "${arrVar[$j]}"
do
textFile=text_${arrRel[$i]}_${arrVar[$j]}.txt
targetDir=Desktop/samples
cat >${textFile} <<EOF
"some tex"
EOF
done #arrVar
cd ../ #cd arrRel
done #for loop over releases
To sum up, there should be 2 folders, rel20 and rel21 and two text files in both. But I just get the folder rel20 and just one text file in it.
I'd appreciate if you can point me why this doesn't work.
I think from what you posted, that his is what you are looking for.
#!/bin/bash
declare -a arrRel=(rel20 rel21)
declare -a arrVar=(pt_el pt_mu)
declare -a arrVarTitle=("electron p_T" "muon p_T")
for i in "${arrRel[#]}"
do
mkdir "$i"
cd "$i"
for j in "${arrVar[#]}"
do
textFile=text_$i_$j.txt
targetDir=Desktop/samples
cat >${textFile} <<EOF
"some tex"
EOF
done #arrVar
cd ../ #cd arrRel
done
Not sure what your intent for arrVarTitle is.
You're indexing the arrays incorrectly. Frankly, the arrays are adding no value, and they're not worth the confusion. Just do:
#!/bin/bash
for i in rel20 rel21; do
( # This open paren is important
mkdir -p $i
cd $i
for j in pt_el pt_mu; do
textFile=text_$i_$j.txt
targetDir=Desktop/samples
cat >${textFile} <<-EOF
"some tex"
EOF
done
) # end subshell to recover previous working directory
done

Get all files from a directory and put them in an array

I have an assignment I am working on, but I am having a problem getting it started. Some of the assignment text is below, which can help guild me in the right direction.
My main problem is getting the list of files into an array. I think if I can do that, the rest should be easy. I can push the files into an array that are passed as arguments, but I don't know how to get all the files from a directory, broken up into each file into an array.
Any help would be greatly appreciated!
Thanks to Benjamin W's comment:
Just use files=(*)
Or, if you want to include hidden files and don't want do get in trouble with empty folders, use this (thanks to Fred's comment):
shopt -s nullglob dotglob
files=(*)
#!/bin/bash
shopt -s nullglob
arr=(/home/*)
for ((i=0; i<${#arr[#]}; i++)); do
echo "${arr[$i]}"
done
This script checks if it has been given any parameters ((( $# == 0 ))), and if not, it uses set -- "$PWD" to set the first positional parameter to the current directory, ..
After that, for f (which is short for for f in "$#") loops over all the parameters for processing.
#!/bin/bash
(( $# == 0 )) && set -- "$PWD"
for f; do
# Do something with f
done

Bash script with using special characters in variable array and copy to folder

I've a new question about a closed question from me.
In the last one I asked for help in fixing a script, which sorts files to folders by it's content. (Bash script which sorts files to folders by it's content; How to solve wildcard in variables?)
Now I have a new problem with that.
The variables had changed. The old ones where single word variables in an array, now I've multiple words with special characters as variable.
Here is my script:
#!/bin/bash
declare -a standorte;
standorte=('Zweigst: 00' 'Zweigst: 03' 'Zweigst: 08')
ls lp.3.* | while read f
do
for ort in "${standorte[#]}"; do
grep -i $ort "$f" >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo Copying $f to $ort
cp "$f" $ort
fi
done
done
Now you see, the "ort" is the folder name. So the script try to copy the file lp.3.* to e.g. Zweigst: 00. But without the escape backslashes it doesn't work. Put I escape charakters into the variable, the script doesn't work, because in the file lp.3.* is no "Zweigst:\ 00".
I think, I must declare a new variable for "ort" where I put the folder names in it.
But I've no idea how to change the for loop. I must say the script, when you found Zweigst: 00 copy this file to folder "zweigst00". I'm sorry my bash script experience is not good at all. I can't change this by my own.
I have multiple (zero to unlimited) lp.3.* files (e.g. lp.3.1, lp.3.2, lp.3.5.2 and so on)
In this files is this text: http://pastebin.com/0ZzCUrpx
You just need to quote the variable:
for ort in "${standorte[#]}"; do
grep -i "$ort" "$f" >/dev/null 2>&1
# ^----^-------- quotes needed
if [ $? -eq 0 ]; then
echo Copying $f to $ort
cp "$f" "$ort"
# ^----^-------- quotes needed
fi
done
Why? Because otherwise this
grep -i $ort "$f" >/dev/null 2>&1
gets expanded as something that grep cannot understand properly:
grep -i Zweigst: 00 "$f" >/dev/null 2>&1
see that it is trying to grep Zgeigst: from file 00.

Bash script - how to fill array?

Let's say I have this directory structure:
DIRECTORY:
.........a
.........b
.........c
.........d
What I want to do is: I want to store elements of a directory in an array
something like : array = ls /home/user/DIRECTORY
so that array[0] contains name of first file (that is 'a')
array[1] == 'b' etc.
Thanks for help
You can't simply do array = ls /home/user/DIRECTORY, because - even with proper syntax - it wouldn't give you an array, but a string that you would have to parse, and Parsing ls is punishable by law. You can, however, use built-in Bash constructs to achieve what you want :
#!/usr/bin/env bash
readonly YOUR_DIR="/home/daniel"
if [[ ! -d $YOUR_DIR ]]; then
echo >&2 "$YOUR_DIR does not exist or is not a directory"
exit 1
fi
OLD_PWD=$PWD
cd "$YOUR_DIR"
i=0
for file in *
do
if [[ -f $file ]]; then
array[$i]=$file
i=$(($i+1))
fi
done
cd "$OLD_PWD"
exit 0
This small script saves the names of all the regular files (which means no directories, links, sockets, and such) that can be found in $YOUR_DIR to the array called array.
Hope this helps.
Option 1, a manual loop:
dirtolist=/home/user/DIRECTORY
shopt -s nullglob # In case there aren't any files
contentsarray=()
for filepath in "$dirtolist"/*; do
contentsarray+=("$(basename "$filepath")")
done
shopt -u nullglob # Optional, restore default behavior for unmatched file globs
Option 2, using bash array trickery:
dirtolist=/home/user/DIRECTORY
shopt -s nullglob
contentspaths=("$dirtolist"/*) # This makes an array of paths to the files
contentsarray=("${contentpaths[#]##*/}") # This strips off the path portions, leaving just the filenames
shopt -u nullglob # Optional, restore default behavior for unmatched file globs
array=($(ls /home/user/DIRECTORY))
Then
echo ${array[0]}
will equal to the first file in that directory.

How do you store a list of directories into an array in Bash (and then print them out)?

I want to write a shell script to show a list of directories entered by a user and then for a user to select one of the directories with an index number based on how many directories there are
I'm thinking this is some kind of array operation, but im not sure how to do this in shell script
example:
> whichdir
There are 3 dirs in the current path
1 dir1
2 dir2
3 dir3
which dir do you want?
> 3
you selected dir3!
$ ls -a
./ ../ .foo/ bar/ baz qux*
$ shopt -s dotglob
$ shopt -s nullglob
$ array=(*/)
$ for dir in "${array[#]}"; do echo "$dir"; done
.foo/
bar/
$ for dir in */; do echo "$dir"; done
.foo/
bar/
$ PS3="which dir do you want? "
$ echo "There are ${#array[#]} dirs in the current path"; \
select dir in "${array[#]}"; do echo "you selected ${dir}"'!'; break; done
There are 2 dirs in the current path
1) .foo/
2) bar/
which dir do you want? 2
you selected bar/!
Array syntax
Assuming you have the directories stored in an array:
dirs=(dir1 dir2 dir3)
You can get the length of the array thusly:
echo "There are ${#dirs[#]} dirs in the current path"
You can loop through it like so:
let i=1
for dir in "${dirs[#]}"; do
echo "$((i++)) $dir"
done
And assuming you've gotten the user's answer, you can index it as follows. Remember that arrays are 0-based so the 3rd entry is index 2.
answer=2
echo "you selected ${dirs[$answer]}!"
Find
How do you get the file names into an array, anyways? It's a bit tricky. If you have find that might be the best way:
readarray -t dirs < <(find . -maxdepth 1 -type d -printf '%P\n')
The -maxdepth 1 stops find from looking through subdirectories, -type d tells it to find directories and skip files, and -printf '%P\n' tells it to print the directory names without the leading ./ it normally likes to print.
#! /bin/bash
declare -a dirs
i=1
for d in */
do
dirs[i++]="${d%/}"
done
echo "There are ${#dirs[#]} dirs in the current path"
for((i=1;i<=${#dirs[#]};i++))
do
echo $i "${dirs[i]}"
done
echo "which dir do you want?"
echo -n "> "
read i
echo "you selected ${dirs[$i]}"
Update: my answer is wrong
Leaving it here to address a common misunderstanding, below the line is erroneous.
To put the directories in an array you can do...
array=( $( ls -1p | grep / | sed 's/^\(.*\)/"\1"/') )
This will capture the dir names, including those with spaces.
Extracting from comments:
literal quotes don't have any effect on string-splitting, so array=( echo '"hello world" "goodbye world"' ) is an array with four elements, not two
#Charles Duffy
Charles also supplied the following link Bash FAQ #50 which is an extended discussion on this issue.
I should also draw attention to the link posted by #Dennis Williamson - why I shouldn't have used ls

Resources