OSX Bash Script working with Files/Folders with spaces in the name - macos

I'm trying to build a BASH script on OS X (10.6/10.7) to process a folder called QCExports which has folders with people's names in it in the format "LAST, First", i.e. "BOND, James".
When I run the following script, everything works but it falls over on folder or filenames with a space in them.
Script Code:
#!/bin/bash
echo "QuickCeph Export Script"
#Set Path to Process & Paths to Copy To
pathToQCExports=/Users/myuser/Desktop/QCExports
sureSmilePath=/Users/myuser/Desktop/QCExportsForSureSmile
sesamePath=/Users/myuser/Desktop/QCExportsForSesame
blankReplace=""
#Process Each Name
find $pathToQCExports -type d | while read name ; do
#escaping the folder with a space in the name
nameParsed=${name/", "/",\ "}
echo "Processing: "$nameParsed
pathSureSmile=${nameParsed/$pathToQCExports/$sureSmilePath}
pathSesame=${nameParsed/$pathToQCExports/$sesamePath}
mkdir $pathSesame
mkdir $pathSureSmile
echo "Folders Created"
#Copy For SureSmile
echo ${pathSureSmile}"/Suresmile-Frontal\ Photo.jpg" ${pathSureSmile}"/Suresmile-Frontal\ Photo.jpg"
#cp `${$pathSureSmile}"/Suresmile-Frontal\ Photo.jpg" ${pathSureSmile}"/Suresmile-Frontal\ Photo.jpg"`
#Copy For Sesame
echo ${pathSesame}"/Suresmile-Frontal\ Photo.jpg" ${pathSesame}"/S02.jpg"
#cp `${pathSesame}"/Suresmile-Frontal\ Photo.jpg" ${pathSesame}"/S02.jpg"`
done
echo "Completed";
Output:
QuickCeph Export Script
Processing: /Users/myuser/Desktop/QCExports/BOND,\ James
mkdir /Users/myuser/Desktop/QCExportsForSesame/BOND,\ James
mkdir: James: File exists
mkdir /Users/myuser/Desktop/QCExportsForSureSmile/BOND,\ James
mkdir: James: File exists
Folders Created
/Users/myuser/Desktop/QCExportsForSureSmile/BOND,\ James/Suresmile-Frontal\ Photo.jpg /Users/myuser/Desktop/QCExportsForSureSmile/BOND,\ James/Suresmile-Frontal\ Photo.jpg
/Users/myuser/Desktop/QCExportsForSesame/BOND,\ James/Suresmile-Frontal\ Photo.jpg /Users/myuser/Desktop/QCExportsForSesame/BOND,\ James/S02.jpg
Completed
On OS X usually in the terminal, you use a \ to escape a space in a folder or filename, but that doesn't seem to work.
I notice that it's interpreting the spaces as a normal space would be interpreted on the command line and thinking I want to execute the command on two files - i.e. it's not passing the \ onwards. I end up with a folder called "Bond,\" and a folder called "James" in the folder I execute the script from.
Note, I deliberately have the cp commands # out at the moment, so they aren't being executed... the problem is the same for both creating the folders & copying the filenames.
If I copy and paste the "echo'd" version of these commands into a terminal window, the commands work! But when BASH executes them, it doesn't respect the . :(
Any ideas?
Thanks!!
John

See my modifications on your script, you don't have to substitute spaces like you tried.
Moreover, you must choose if you backslash the spaces or if you are using quotes.
The simple way is to use double quotes.
Good doc about quotes, see http://mywiki.wooledge.org/Quotes and http://wiki.bash-hackers.org/syntax/words
#!/bin/bash
echo "QuickCeph Export Script"
#Set Path to Process & Paths to Copy To
pathToQCExports=/Users/myuser/Desktop/QCExports
sureSmilePath=/Users/myuser/Desktop/QCExportsForSureSmile
sesamePath=/Users/myuser/Desktop/QCExportsForSesame
blankReplace=""
#Process Each Name
find $pathToQCExports -type d | while read nameParsed ; do
echo "Processing: $nameParsed"
pathSureSmile="${nameParsed/$pathToQCExports/$sureSmilePath}"
pathSesame="${nameParsed/$pathToQCExports/$sesamePath}"
mkdir "$pathSesame"
mkdir "$pathSureSmile"
echo "Folders Created"
#Copy For SureSmile
echo "${pathSureSmile}/Suresmile-Frontal Photo.jpg" "${pathSureSmile}/Suresmile-Frontal Photo.jpg"
#Copy For Sesame
echo "${pathSesame}/Suresmile-Frontal Photo.jpg" "${pathSesame}/S02.jpg"
done
echo "Completed"

Related

BASH Script for creating multiple directories, moving files, and then renaming said files

I am trying to make a bash script to create directories with the same name as each file in a given directory, then move said files to their respective directories, and then rename the files.
Basically - a quantum chemistry program that I use requires that the input files be named "ZMAT". So, if I have multiple jobs, I currently need to manually create directories, and then move the ZMAT files into them (can only run one job per folder).
When I run my code, I get "binary operator expected". I am not sure what this means. Some help please.
Here is what I have so far:
#!/bin/bash
if [ -e *.ZMAT ];
then
echo "CFOUR Job Detected"
for INPFILE in *.ZMAT; do
BASENAME=$(basename $INPFILE )
INPFILE=$BASENAME.ZMAT
OUTFILE=$BASENAME.out
XYZFILE=$BASENAME.xyz
ERRORFILE=$BASENAME.slu
if [ ! -e $ERRORFILE ];
then
# Create folder in scratch directory with the basename
mkdir /scratch/CFOUR/$BASENAME
# Move the file to its directory
mv -f $INPFILE /scratch/CFOUR/$BASENAME
# cd to the new directory
cd /scratch/CFOUR/$BASENAME
# Change the file name to just ZMAT
mv -f $INPFILE ZMAT
echo "Submitting CFOUR Job"
# Submit to scheduler
#RUN_COMMAND="sbatch -J $BASENAME _CFOUR_MRCC_SLURM.SUB"
#eval $RUN_COMMAND
else
echo "Error File Detected - Not Submitting Job"
fi
done
fi
An alternative would be to create symlinks to the original files.
As you said before, each ZMAT symlink would need to be in its own directory.
The upside is that the original data doesn't move, so less risk of breaking it, but the tool you want to use should read the symlinks as if they are the files it is looking for.
This one-liner creates an out directory in the current folder that you could subsequently move wherever you want it. You could easily create it where you do want it by replacing "out" with whatever absolute path you wanted
for i in *.ZMAT; do mkdir -p out/$i ; ln -s $PWD/$i out/$i/ZMAT ; done
I believe I have solved my problem. Here is the new script, which appears to be working fine. Any input is welcome though!
#!/bin/bash
SUBDIR=$(pwd)
for i in *.ZMAT; do
BASENAME=$(basename $i .ZMAT)
INPFILE=$BASENAME.ZMAT
OUTFILE=$BASENAME.out
XYZFILE=$BASENAME.xyz
ERRORFILE=$BASENAME.slu
if [ ! -e $ERRORFILE ];
then
mkdir /scratch/CFOUR/$BASENAME # Create Scratch Folder
cp $INPFILE /scratch/cdc/CFOUR/$BASENAME # Move Input to Scratch
cd /scratch/CFOUR/$BASENAME #cd to Scratch Folder
mv -f $INPFILE ZMAT # Change Input Name
echo "Submitting CFOUR Job"
# Submit to scheduler
#RUN_COMMAND="sbatch -J $BASENAME _CFOUR_MRCC_SLURM.SUB"
#eval $RUN_COMMAND
cd $SUBDIR #Go back to SUBDIR
else
echo "Error File Already Exists"
fi
done

Nested for loops for directory in bash script

for d in dirlist;
do
cd $d
echo $PWD "primul"
for p in *.HEIC;
do
echo $PWD "din al doilea"
if [ -e "COPY ${p%.*}.jpg" ]; then
echo "COPY ${p%.*}.jpg exists"
else
convert $p "COPY ${p%.*}.jpg"
echo "COPY ${p%.*}.jpg created"
fi
done
done
I have written a script to convert all files that are .HEIC to .jpg. Now I have a structure of folders that is
/main/2019/01
/main/2019/03
/main/2018/12
/main/2018/11
/main/2018/10
/main/2018/09
And I want to run the second for in each directory.
What I get when I run the commands below is only a list of directories and a mention that there are no files to convert.
How do I change the directory using the first for and run the second for in that directory?
cd - changes to the previous directory. That one solution to put it after the first done (second for cycle).
pushd and popd using stack to handle directory changes
check out: https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html

Prompt for file path: home directory alias and case-sensitive tab completion

I am writing a shell script that prompts the user for a file path:
read -e -p "Enter the path to the file: " FILEPATH
I am then using this file path to perform operations – namely to compress a folder.
(cd "$FILEPATH"; tar -cvz *) > /tmp/torrent.tar.gz;
At the prompt, if I use the ~ alias (home directory), then the shell script doesn't seem to understand this, as the tar function compresses the wrong path. Is there anyway I can allow for this alias?
Also, tab completion seems to be case-sensitive at the prompt. I was wondering how I can change that?
Example using eval:
read -e -p "Enter the path to the file: " FILEPATH
eval FILEPATH=$FILEPATH
cd $FILEPATH
echo $PWD
In your case it becomes:
read -e -p "Enter the path to the file: " FILEPATH
eval FILEPATH=$FILEPATH
(cd "$FILEPATH"; tar -cvz *) > /tmp/torrent.tar.gz;
To deal with spaces you can use sed:
read -e -p "Enter the path to the file: " FILEPATH
FILEPATH=$(echo $FILEPATH | sed 's/ /\\ /')
eval FILEPATH=$FILEPATH
cd "$FILEPATH"
echo $PWD
You could apply the substitution yourself like this:
filepath=${filepath/\~/$HOME}
I don't know whether there's a way to get the shell to do it for you.
Here's an answer to your other question: https://superuser.com/questions/90196/case-insensitive-tab-completion-in-bash

Bash script to find a directory, list its contents and sub-folders info

I want to write a script that will:
1- locate folder "store" on a *nix filesystem
2- move into that folder
3- print list of contents with last modification date
4- calculate sub-folders size
This folder's absolute path changes from server to server, but the folder name remains the same always.
There is a config file that contains the correct path to that folder though, but it doesn't give absolute path to it.
Sample Config:
Account ON
DIR-Store /hdd1
Scheduled YES
ِAccording to the config file the absolute path would be "/hdd1/backup/store/"
I need the script to grep the "/hdd1" or anything beyond the word "DIR-Store", add "/backup/store/" to it, move into folder "store", print list of its contents, and calculate the sub-folder's size.
Until now I manually edit the script on each server to reflect the path to the "store" folder.
Here is a sample script:
#!/bin/bash
echo " "
echo " "
echo "Moving Into Directory"
cd /hdd1/backup/store/
echo "Listing Directory Content"
echo " "
ls -alh
echo "*******************************"
sleep 2
echo " "
echo "Calculating Backup Size"
echo " "
du -sh store/*
echo "********** Done! **********"
I know I could use grep
cat /etc/store.conf | grep DIR-Store
I just don't know how to get around selecting the path, adding the "/backup/store/" and moving ahead.
Any help will be appreciated
You can use cut to extract columns from the configuration file. Specify a field delimiter with -d. Cut only allows single-character delimiters (like e.g. a single space) and there are certainly gazillion other ways to split the line.
Then just manually append the know subdirectory to the stem.
STORE=$(grep DIR-Store /etc/store.conf | cut -d" " -f2)
DIR="${STORE}/backup/store"
pushd "${DIR}"
ls -alh
sleep 2
du -sh *
popd
If there are no spaces on that line except for the one(s) between "DIR-Store" and the directory:
dir=($(grep "DIR-Store" /etc/store.conf))
dir="${dir[1]}/backup/store"
cd "$dir" # or pushd "$dir"
or this keys on the first slash rather than a space:
dir=$(grep "DIR-Store" /etc/store.conf)
dir="/${dir#*/}/backup/store"
cd "$dir" # or pushd "$dir"

Reading and tab-completing a directory name in a bash script

I want to prompt the user for a directory name, and have them able to tab-complete to a valid directory.
So far, I've got tab-completion working for both files and directories using "read -e". I only want directories to be autocompleted when the user presses tab.
Here's what I have:
echo "Enter a directory"
read -e -p "> " DEST
How can I make bash only return a list of directories when the user presses tab, rather than a list of files and directories?
An alternate approach that gives you a lot of flexibility is to use compgen; see my answer here for details.
Here's my quick take at the problem. For some reason I had to actually use bash and not sh on my computer, due to the use of pushd and popd. I think it's well commented enough for me to not explain it any further.
#!/bin/sh
tempdir=`mktemp -d`
# save the current directory
pushd .
# make a new folder, then make a bunch of new directories
# mirroring those in our current directory
for i in $(find . -type d); do mkdir "$tempdir/$i" ; done
# change to the temporary directory
cd "$tempdir"
echo "Enter a directory"
read -e -p ">" DEST
echo "You told me $DEST"
# return to our original directory
popd
# clear out that temporary directory we made
rm -rf "$tempdir"
But Jacob's response is probably more efficient and cleaner than mine.

Resources