Looping through files with bashscript not working - bash

I have this code which works:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
for entry in "$DIR"/*
do
echo "$entry"
done
But when I try to do this,
for entry in "$DIR/Images"/*
do
echo "$entry"
done
then my script tries to access a file with the name $DIR/Images/*
When I do this in attempt to fix the problem:
for entry in "$(DIR/Images)"/*
do
echo "$entry"
done
the script prints out all files in the root directory of my mac. How Do I make my script loop through all files in $DIR/Images/?
assuming of course that Images exists

for entry in "$DIR"/Images/*
works, per your comment.
I'm not convinced there is a difference between "$DIR/Images"/* and "$DIR"/Images/*.
for entry in "$DIR/Images/*"
Will not work, because asterisks are not expanded in quotes, and $DIR/Images/* will not work if $DIR contains spaces.
However,
mkdir "folder with spaces"
a="folder with spaces"
mkdir "$a"/bc
touch "$a"/bc/def.txt
echo "$a"/bc/*
>> folder with spaces/bc/def.txt
echo "$a/bc"/*
>> folder with spaces/bc/def.txt
I.e., both ways work on my machine.

Related

Use of CD in Bash For Loop - only getting relative path

I have a small script that I use to organizes files in directories, and now I am attempting to run it on a folder or directories. MasterDir/dir1, MasterDir/dir2, etc.
Running the following code on MasterDir results in an error as the $dir is only a relative path, but I can't figure out how to get the full path of $dir in this case
for dir in */; do
echo $dir
cd $dir
cwd="$PWD"
mkdir -p "VSI"
mv -v *.vsi "$cwd/VSI"
mv -v _*_ "$cwd/VSI"
done
I'd suggest using parentheses to run the loop body in a subshell:
for dir in */; do
(
echo $dir
cd $dir
cwd="$PWD"
mkdir -p "VSI"
mv -v *.vsi "$cwd/VSI"
mv -v _*_ "$cwd/VSI"
)
done
Since that's running in a subshell, the cd command won't affect the parent shell executing the script.
The problem you are having is that after you cd "$dir" the first time, you are one directory below where you generated your list of directories with for dir in */. So the next time you call cd "$dir" it fails because you are still in the first subdirectory you cd'ed into and the next "$dir" in your list is one level above.
There are several ways to handle this. One simple one is to use pushd to change to the directory instead of cd, so you can popd and return to your original directory. (though in this case you could simply add cd .. to change back to the parent directory since you are only one-level deep)
Using pushd/popd you could do:
for dir in */; do
echo $dir
pushd "$dir" &>/dev/null || {
printf "error: failed to change to %s\n" "$dir" >&2
continue
}
cwd="$PWD"
mkdir -p "VSI" || {
printf "error: failed to create %s\n" "$cwd/VSI" >&2
continue
}
mv -v *.vsi "$cwd/VSI"
mv -v _*_ "$cwd/VSI"
popd &>/dev/null || {
printf "error: failed to return to parent dir\n" >&2
break
}
done
(note: the || tests validate the return of pushd, mkdir, popd causing the loop to either continue to the next dir or break the loop if you can't return to the original directory. Also note the &>/dev/null just suppresses the normal output of pushd/popd, and redirection of output to >&2 sends the output to stderr instead of stdout)
As mentioned in the comment, you can always use readlink -f "$dir" to generate the absolute path to "$dir" -- though it's not really needed here.
This is one of the reasons I tend to avoid using cd in shell scripts -- all relative file paths change meaning when you cd, and if you aren't very careful about that you can get into trouble. The other is that cd can fail (e.g. because of a permissions error), in which case you'd better have an error check & handler in place, or something even weirder will happen.
IMO it's much safer to just use explicit paths to refer to files in other directories. That is, instead of cd somedir; mkdir -p "VSI", use `mkdir -p "somedir/VSI". Here's a rewrite of your loop using this approach:
for dir in */; do
echo $dir
mkdir -p "${dir}/VSI"
mv -v "${dir}"/*.vsi "${dir}/VSI"
mv -v "${dir}"/_*_ "${dir}/VSI"
done
Note: the values of $dir will end with a slash, so using e.g. ${dir}/VSI will give results like "somedir//VSI". The extra slash is redundant, but shouldn't cause trouble; I prefer to use it for clarity. If it's annoying (e.g. in the output of mv -v), you can leave off the extra slash.

Can't iterate correctly over specific files in folder with endings {*-enterprise.ipa,*.apk}

I have a folder containing these app files:
mles:fairs-ionic mles$ ls build
build/com.solutions.enterprise.fairs.dev_v2.2.0.17_20170720_1423-enterprise.ipa
build/com.solutions.enterprise.fairs_v2.2.0.17_20170720_1423-enterprise.ipa
build/de.fairs.dev_v2.2.0.17_20170720_1423-enterprise.apk
build/de.fairs_v2.2.0.17_20170720_1423-appstore.ipa
build/de.fairs_v2.2.0.17_20170720_1423-enterprise.apk
I need to uppload all of them except the *-appstore.ipa one. More specifically these one:
mles:fairs-ionic mles$ ls build/{*-enterprise.ipa,*.apk}
build/com.solutions.enterprise.fairs.dev_v2.2.0.17_20170720_1423-enterprise.ipa
build/com.solutions.enterprise.fairs_v2.2.0.17_20170720_1423-enterprise.ipa
build/de.fairs.dev_v2.2.0.17_20170720_1423-enterprise.apk
build/de.fairs_v2.2.0.17_20170720_1423-enterprise.apk
In my bash script I've tried:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
appFiles=($(cd ${DIR}/build;ls {*-enterprise.ipa,*.apk}))
echo ${appFiles};
echo "loop"
for appFile in "${appFiles[#]}"
do
echo ${appfile}
#app_upload "${appfile}"
done
this yields:
mles:fairs-ionic ben$ ./test.sh
com.solutions.enterprise.fairs.dev_v2.2.0.17_20170720_1423-enterprise.ipa
loop
appFiles only contains one row, and the appfile variable in the loop is always empty.
How can I iterate over all the files in the build folder except the .ipa files with appstore in the filename (build/de.fairs_v2.2.0.17_20170720_1423-appstore.ipa) ?
Variable appfile is not defined only appFile, So change
echo ${appfile}
to
echo ${appFile}
Edit the line displaying all the data
echo ${appFiles};
To
echo ${appFiles[*]};
You can just do this simply with extended glob features (see Options which change globbing behavior) provided by bash, turn the options on and stash the values to an array. Run it outside the build/ folder.
shopt -s extglob nullglob
fileList=( build/!(*-appstore.ipa) )
now loop over the array and do whatever you want to do with it.
for file in "${fileList[#]}"; do
printf "%s\n" "$file"
done
You don't need to create an array of selected files as you can directly iterate them using a glob with brace expansion like this:
for file in build/*{-enterprise.ipa,.apk}; do
echo "$file"
# app_upload "$file"
done

Mac OS X Terminal batch rename...but with folder paths

I tried incorrectly to add my question on to a very similar thread w/ good solutions here:
mac os x terminal batch rename
I have essentially the same question, but I'm wanting to do this and change the folder path when renaming. Here is what I asked:
Would any of these solutions work to change underscores to a folder path? For example, I have mbox files on one level that need to be nested, such as:
TopLevel_NextLevel_mbox
TopLevel_NextLevel_FinalLevel_mbox
I'd like to automatically put these in a hierarchy like so:
TopLevel/NextLevel/mbox
TopLevel/NextLevel/FinalLevel/mbox
Can this be done? When I try simple replacement with "/", I get this:
fred$ for f in *_mbox; do mv "$f" "${f/_//}"; done
mv: rename TopLevel_NextLevel_mbox to TopLevel/NextLevel_mbox: No such file or directory
Looks like it just tries to sub in the "/", but then gets confused because there is no current folder TopLevel w/ NextLevel_mbox inside it...
Thanks,
Fred
The basics of making this work starts with the process of creating an array from the current directories that contain *mbox. Each array key then contains the resulting delimited word found between the underscores:
TopLevel_NextLevel_mbox
Is transformed into an array like this:
( TopLevel, NextLevel, mbox )
From there we create the first directory TopLevel then perform a cd followed by mkdir on the next key — repeating the process until there are no more keys. By doing this each array key creates a new nested directory (as a bonus it also copies any data from the original directory into the new one whilst keeping it's structure).
Create Nested Folders from Original
#!/bin/bash
DIR=$PWD
for f in *mbox
do cd $DIR
if [[ -d $f ]]; then
ARR=(${f//_/ }); n=0
for i in "${ARR[#]}"
do echo $n
if [[ $n -eq 0 ]]; then
mkdir -p $i && cp -R $f/* $i && cd $_
else
mkdir -p $i && cd $_
fi
let n++
done
fi
done
Pseudo One-liner
DIR=$PWD; for f in *mbox; do cd $DIR; if [[ -d $f ]]; then ARR=(${f//_/ }); n=0; for i in "${ARR[#]}"; do if [[ $n -eq 0 ]]; then mkdir -p $i && cp -R $f/* $i && cd $_; else mkdir -p $i && cd $_; fi; let n++; done; fi; done
This is the same exact script as the one above it, however, it's formatted to be one line. * The script leaves the original directories intact (I'll leave the exercise of removing them up to the OP).

Script bash shell - Making an alternative trashcan

I've made this script, but I have difficulties to write the code for this situation:
in the trashcan there should be no file with the same name; in which case they should be renamed.
How can I solve?
Here is my code:
#!/bin/bash
help() {
echo "Options:"
echo "\"safe-rm pathname\" to delete, where the pathname can be absolute or relative"
echo "\"safe-rm --recover original pathname\" (including the /) to recover and restore a file or a directory in the original position"
echo "\"safe-rm --list\" to lists the trashcan's content"
echo "\"safe-rm --search\" to search a file in the trashcan"
echo "\"safe-rm --delete-older-than\" to delete files older than certain days"
}
delete() {
if [ ${PARAM1:0:1} = "/" ]; then
echo "You have insert an absolute pathname"
mkdir -p $(dirname $TRASH$PARAM1)
mv $PARAM1 $TRASH$PARAM1
else
echo "You have insert a relative pathname"
mkdir -p $(dirname $TRASH$(pwd)/$PARAM1)
mv $PARAM1 $TRASH$(pwd)/$PARAM1
fi
}
readonly TRASH=$HOME/.Trash;
readonly PARAM1=$1;
readonly PARAM2=$2;
mkdir -p $TRASH;
case "$PARAM1" in
"")
help
;;
--list)
echo "Trashcan's content"
cd $TRASH
find *
;;
--delete-older-than)
echo "Delete the files older than $PARAM2 days"
find $TRASH -mtime +$PARAM2 | xargs rm -rf
;;
--search)
echo "Search $PARAM2 among the trashcan's files"
cd $TRASH
find -name *$PARAM2*
;;
--recover)
echo "Recover the file/directory in the original position"
mkdir -p $(dirname $PARAM2)
mv $TRASH$PARAM2 $PARAM2
;;
*)
echo "Security delete a file/directory"
delete
;;
esac
exit 0
Quick and dirty solution:
if [[ -f $TRASH$PARAM ]]; then
mv "$PARAM1" "$TRASH$PARAM$RANDOM$RANDOM" # file exists
else
mv "$PARAM1" "$TRASH$PARAM" # ok, it is fine, file does not exist
fi
Also please note that you have to quote every variable in your script when it is passed as a parameter.
if [ ${PARAM1:0:1} = "/" ]; then must be changed to if [ "${PARAM1:0:1}" = "/" ]; then or even better if [[ ${PARAM1:0:1} = "/" ]]; then
mkdir -p $(dirname $TRASH$PARAM1) to mkdir -p "$(dirname "$TRASH$PARAM1")"
And so on...
Consider generating a trailing sum like sha1sum on your files when they are stored on the the trash can to prevent having conflicts with similar files. e.g.
$HOME/.Trash/home/user/same/path/same_name.9ce1f394b955306f7c450cbf0d96d2f17f6a1394
$HOME/.Trash/home/user/same/path/same_name.b0dc31b1919c02932892b59d0c0e365cd75629c6
When restoring those files you just have removed the sum like
/home/user/same/path/same_name
The solution could also prevent duplicates of truly the same files for the probable uniqueness of what sums could do.
If you trust sums enough you could also opt to not store the directories in the trash. Just their signatures with an extra info file on it like:
$HOME/.Trash/same_name.9ce1f394b955306f7c450cbf0d96d2f17f6a1394
$HOME/.Trash/same_name.9ce1f394b955306f7c450cbf0d96d2f17f6a1394.info
Where info contains the absolute path of the directory where the file is located.
/home/user/same/path/
You could even add other attributes on it like directory permissions, etc.
[File]
/home/user/same/path/same_name
[Attributes]
/home[ TAB ]USER:GROUP[ TAB ]0755
/home/user[ TAB ]USER:GROUP[ TAB ]0755
/home/user/same[ TAB ]USER:GROUP[ TAB ]0755
/home/user/same/path[ TAB ]USER:GROUP[ TAB ]0755
/home/user/same/path/same_name[ TAB ]USER:GROUP[ TAB ]0644
Basically sums are just concepts but you could add more tricks on your own base on it to make file existence in trash a little more certain to be unique, only that you have to consider that it could no longer prevent files with that are really the same to exist as two entries which could have been not necessary.
Also of course on your script if you want to support filenames with spaces and likes, always place your variables inside double-quotes to prevent those from word splitting which means they would could later be interpreted as two or more arguments to the command causing syntax error to the command or unexpected results in which some may be irrevocable.
do something "$var" "${etc}xyz"
Actually, I wrote that back in 2010. Now there is a trash-cli package for Ubuntu.
http://wiki.linuxquestions.org/wiki/Scripting#Command_Line_Trash_Can

Bash: Creating subdirectories reading from a file

I have a file that contains some keywords and I intend to create subdirectories into the same directory of the same keyword using a bash script. Here is the code I am using but it doesn't seem to be working.
I don't know where I have gone wrong. Help me out
for i in `cat file.txt`
do
# if [[ ! -e $path/$i ]]; then
echo "creating" $i "directory"
mkdir $path/$i
# fi
grep $i file >> $path/$i/output.txt
done
echo "created the files in "$path/$TEMP/output.txt
You've gone wrong here, and you've gone wrong here.
while read i
do
echo "Creating $i directory"
mkdir "$path/$i"
grep "$i" file >> "$path/$i"/output.txt
done < file.txt
echo "created the files in $path/$TEMP/output.txt"
78mkdir will refuse to create a directory, if parts of it do not exist.
e.g. if there is no /foo/bar directory, then mkdir /foo/bar/baz will fail.
you can relax this a bit by using the -p flag, which will create parent directories if necessary (in the example, it might create /foo and /foo/bar).
you should also use quotes, in case your paths contain blanks.
mkdir -p "${path}/${i}"
finally, make sure that you are actually allowed to create directories in $path

Resources