Loop through Filenames with spaces Bash using variables - bash

I am using the following script to iterate over a folder structure, however folders contains space in between is skipped.
for i in $(echo $File_Directory | sed "s/\// /g")
do
if [ -d $i ]
then
echo "$i directory exists."
else
echo "Creating directory $i"
`mkdir $i`
fi
done
Appreciate help on this..

Looks to me like the whole loop ought to be mkdir -p "$File_Directory" ...
But assuming he meant to put them all in the same dir:
$: File_Directory=a/b/c/d/e/f/g
$: ( IFS=/; for d in $File_Directory; do mkdir -p "$d"; done; )
That does play a little fast and loose with the parameter parsing though.

Related

How to add a character to folder names when using the ls command?

I would like to display a character at the beginning of all folder names when typing the ls command.
So instead of this:
ls
Folder-1 Folder-2 file.txt
It displays this:
ls
📁Folder-1 📁Folder-2 file.txt
Is there a script I can write in my .bash_profile to do this?
You can make a custom script that makes it for you:
#!/bin/bash
lsmod=($(ls)) # Convert ls output to an array
for folder in ${lsmod[#]}; do # Iterate over ls results
if [[ -d $folder ]]; then # If this is a folder then...
echo "=> $folder" # Put your char
else # If not, display normally
echo "$folder"
fi
done
Folders are pointed out with a =>. Hope this helps.
One line approach:
for i in `ls`; do echo $([[ -d "$i" ]] && echo "=> $i" || echo "$i"); done;

Bash check is file with variable name inside loop exists

I would like to check if a file exists. Of course this is explained in many places. Now I am inside a loop like:
for ((l=0;l<5;l+=1));
do
if -a FILENAMEl #FILENAME contains l!!!!!!!!!
then "FILENAMEl exists"
else
do
.............
fi
done
Any ideas?
Thanks so much!!!
The main problem is that you are mixing the syntax of a variable name l with that of a file name. If you wish to use them together, to form part of a filename with a variable, you need a syntax break (caused by "$"), or use braces ({}).
If the file name has a variable in the middle, then braces work best. For example: "my_file_${l}_head.txt" would create files like my_file_1_head.txt, my_file_2_head.txt, etc.
Here is your original example corrected:
for ((l=0;l<5;l+=1))
do
if test -a FILENAME$l
then echo "FILENAME$l exists"
else echo "FILENAME$l doesn't exist"
fi
done
However, I wouldn't write code this way.
I only took your example and changed it as little as possible to show you the essential difference.
Here's another way to write it, using a more DRY (Don't Repeat Yourself) approach:
for l in {1..5}; do
file="filename$l"
if [[ -a "$file" ]]; then
echo "$file exists"
else
echo "$file does not exist"
fi
done
If you want more minimalism, here's yet another approach:
for l in {1..5}; do f="filename$l"
[[ -a "$f" ]] && echo "$f exists" || echo "$f does not exist"
done
Now, if you need to do something other than just print out the status, using function calls to make the extra work modular works well:
for l in {1..5} ; do f="$filename$l"
[[ -a "$f" ] && process_file $f || non_existant_file $f
done
Then, elsewhere, you should define both process_file and non_existant_file:
process_file() {
local file="$1"
# do whatever is needed for an existing file
}
non_existant_file() {
local file="$1"
# do whatever is needed for a non-existant file
}
Assume you're trying to find which files exist in the filename format file1.csv, file2.csv, etc...
for i in {1..5};
do f="file$i.csv";
if test -e $f;
then echo "$f exists";
else echo "$f does not exist";
fi
done
Perhaps what you need is simply a find
find . -name "file?.csv" -size +10k
You can restrict the file name to suffix 1..5 and do an action on the find result (check for find's -exec or more generally xargs as below).
find . -name "file[1-5].csv" -size +10c | xargs head -1

Script is not glob-expanding, but works fine when running the culprit as a minimalistic example

I've been trying for hours on this problem, and cannot set it straight.
This minimal script works as it should:
#!/bin/bash
wipe_thumbs=1
if (( wipe_thumbs )); then
src_dir=$1
thumbs="$src_dir"/*/t1*.jpg
echo $thumbs
fi
Invoke with ./script workdir and a lot of filenames starting with t1* in all the sub-dirs of workdir are shown.
When putting the above if-case in the bigger script, the globbing is not executed:
SRC: -- workdir/ --
THUMBS: -- workdir//*/t1*.jpg --
ls: cannot access workdir//*/t1*.jpg: No such file or directory
The only difference with the big script and the minimal script is that the big script has a path-validator and getopts-extractor. This code is immediately above the if-case:
#!/bin/bash
OPTIONS=":ts:d:"
src_dir=""
dest_dir=""
wipe_thumbs=0
while getopts $OPTIONS opt ; do
case "$opt" in
t) wipe_thumbs=1
;;
esac
done
shift $((OPTIND - 1))
src_dir="$1"
dest_dir="${2:-${src_dir%/*}.WORK}"
# Validate source
echo -n "Validating source..."
if [[ -z "$src_dir" ]]; then
echo "Can't do anything without a source-dir."
exit
else
if [[ ! -d "$src_dir" ]]; then
echo "\"$src_dir\" is really not a directory."
exit
fi
fi
echo "done"
# Validate dest
echo -n "Validating destination..."
if [[ ! -d "$dest_dir" ]]; then
mkdir "$dest_dir"
(( $? > 0 )) && exit
else
if [[ ! -w "$dest_dir" ]]; then
echo "Can't write into the specified destination-dir."
exit
fi
fi
echo "done"
# Move out the files into extension-named directories
echo -n "Moving files..."
if (( wipe_thumbs )); then
thumbs="$src_dir"/*/t1*.jpg # not expanded
echo DEBUG THUMBS: -- "$thumbs" --
n_thumbs=$(ls "$thumbs" | wc -l)
rm "$thumbs"
fi
...rest of script, never reached due to error...
Can anyone shed some lights on this? Why is the glob not expanded in the big script, but working fine in the minimalistic test script?
EDIT: Added the complete if-case.
The problem is that wildcards aren't expanded in assignment statements (e.g. thumbs="$src_dir"/*/t1*.jpg), but are expanded when variables are used without double-quotes. Here's an interactive example:
$ src_dir=workdir
$ thumbs="$src_dir"/*/t1*.jpg
$ echo $thumbs # No double-quotes, wildcards will be expanded
workdir/sub1/t1-1.jpg workdir/sub1/t1-2.jpg workdir/sub2/t1-1.jpg workdir/sub2/t1-2.jpg
$ echo "$thumbs" # Double-quotes, wildcards printed literally
workdir/*/t1*.jpg
$ ls $thumbs # No double-quotes, wildcards will be expanded
workdir/sub1/t1-1.jpg workdir/sub2/t1-1.jpg
workdir/sub1/t1-2.jpg workdir/sub2/t1-2.jpg
$ ls "$thumbs" # Double-quotes, wildcards treated as literal parts of filename
ls: workdir/*/t1*.jpg: No such file or directory
...so the quick-n-easy fix is to remove the double-quotes from the ls and rm commands. But this isn't safe, as it'll also cause parsing problems if $src_dir contains any whitespace or wildcard characters (this may not be an issue for you, but I'm used to OS X where spaces in filenames are everywhere, and I've learned to be careful about these things). The best way to do this is to store the list of thumb files as an array:
$ src="work dir"
$ thumbs=("$src_dir"/*/t1*.jpg) # No double-quotes protect $src_dir, but not the wildcard portions
$ echo "${thumbs[#]}" # The "${array[#]}" idiom expands each array element as a separate word
work dir/sub1/t1-1.jpg work dir/sub1/t1-2.jpg work dir/sub2/t1-1.jpg work dir/sub2/t1-2.jpg
$ ls "${thumbs[#]}"
work dir/sub1/t1-1.jpg work dir/sub2/t1-1.jpg
work dir/sub1/t1-2.jpg work dir/sub2/t1-2.jpg
You might also want to set nullglob in case there aren't any matches (so it'll expand to a zero-length array).
In your script, this'd come out something like this:
if (( wipe_thumbs )); then
shopt -s nullglob
thumbs=("$src_dir"/*/t1*.jpg) # expanded as array elements
shopt -u nullglob # back to "normal" to avoid unexpected behavior later
printf 'DEBUG THUMBS: --'
printf ' "%s"' "${thumbs[#]}"
printf ' --\n'
# n_thumbs=$(ls "${thumbs[#]}" | wc -l) # wrong way to do this...
n_thumbs=${#thumbs[#]} # better...
if (( n_thumbs == 0 )); then
echo "No thumb files found" >&2
exit
fi
rm "${thumbs[#]}"
fi

List only Folders not files

I have the following srcipt which should show me all folders in the directory, but at the moment the script list also the files under /var/www
declare -a dirs
i=1
for d in /var/www/*
do
dirs[i++]="${d%/}"
done
for((i=1;i<=${#dirs[#]};i++))
do
echo $i "${dirs[i]}"
done
What shoult i change to list only folders in the array?
How about:
printf "%s\n" /var/www/*/
You could perform a check whether it's a directory. Say:
[ -d "$d" ] && dirs[i++]="${d%/}"
instead of saying:
dirs[i++]="${d%/}"
Quoting help test:
-d FILE True if file is a directory.
for i in *
do
[ -d ${i} ] && echo $i
done
Instead of:
for d in /var/www/*
...do it like this (notice the trailing /):
for d in /var/www/*/
It will only match directories.

Why String concatenation of extracted file names does not work?

I have a file "a.txt", that contains file names with paths.
a.txt:
/root/chan/properties.lo
/root/attributes.cc
/root/chan/eagle/bath.ear
I would like to extract these file names and put them in one variable this way:
#!/bin/bash
for i in $(cat a.txt); do
o+=$(basename $i)
done
echo $o
But it does not work.
I am geting:
feedBackMailConfiguration.xmltiess
Please, help.
while read -r i;
do
o=$o" "$(basename $i)
done < a.txt
echo $o
The above will do it.
(edits for copy and paste errors)
More edits: just tried this (terminator, ubuntu) and it give the right result:
while read -r i; do o="$o $(basename $i)";done < a.txt
You just need to add a space concatenation to your script, like this:
#!/bin/bash
for i in $(cat a.txt); do
# Check the " " at the end of the following line
o+=$(basename $i)" "
done
echo $o
And it would work
"o+=..." is not really portable (which version of bash allows it? I never saw it until your mention of it? It looks like perl mechanisms)
Edit2 : following vladintok remarks on what the output is, I add DEBUG infos to try to pinpoints the problem
Edit3 : another step of trying to take out of the way every possible explanation for the weird output reported... I unalias, and unset any function named "o" and "basename" ...
Try:
#!/bin/bash
unalias o 2>/dev/null
unalias basename 2>/dev/null
unset o 2>/dev/null
unset basename 2>/dev/null
for i in $(cat a.txt); do
printf 'DEBUG: i is: %s\n' "$i"
o="${o} $(basename $i)"
done
printf 'DEBUG: final o is: %s\n' "$o"
echo $o
#end of the script.
(of course the above is a SINGLE file. Name it 'test.bash", make it executable: chmod +x test.bash, and then execute it : ./test.bash)
Edit1: I corrected the o=$o" "$(basename $i) into o="${o} $(basename $i)" as pointed out by Pantoine. Otherwise, toto=$toto" "titi : would assign to toto everything until the first space (which will be somewhere inside $toto, after the first iteration or if a basename contained spaces)...

Resources