loop for file location until file exists in bash - bash

I have created this function:
function promptFile()
{
while true;
do
read -p "Please provide a full path [q to quit]: " file
if [ $file == q ]; then
echo "Exiting.."
return 1
fi
if [ ! -f $file ]; then
echo "File does not exist, please try again"
else
echo $file
break
fi
done
}
To prompt a user for file location, ask again if file does not exist, and save the output to a variable if it does, the function is called:
tempLoc=$(promptFile)
if [ !tempLoc ]; then
fileLocation=$tempLoc
fi
Everything works well unless someone write a bad file location, then the echo is not shown until someone clicks q or inputs an existing file location.
in which case the echo message will be printed * the number of bad inputs, as follows.
[root#tsting:0]# ./tst
Please provide a full path [q to quit]: tst1
Please provide a full path [q to quit]: tst2
Please provide a full path [q to quit]: tst3
Please provide a full path [q to quit]: tst4
Please provide a full path [q to quit]: q
File does not exist File does not exist File does not exist File does not exist Exiting..
[root#tsting:0]#
I'm guessing this happens because the loop collapses back printing all the echos as it happens, is there a way to avoid this and just print the echo when the wrong file location is entered ?

Write the error to stderr
echo "File does not exist, please try again" >&2
You are saving all output from the function into the variable tempLoc, so even if the user inputs a valid file it will have a load of junk in the variable with it.
Stderr is where error messages should go anyway though, so it's good practice to send them there even without this problem.

Several things here:
You don't need () with "function" (and visa versa). () is usually preferred, (except in Korn shell).
ALWAYS write error messages to stderr: >&2, that is the main reason why it does not work. There are TWO instances where this is required.
Nothing to do with your issue, but it is a good idea to quote variable values, especially filenames: "$file". This is in case someone has whitespace in the filename. Not that anyone in their right mind would ever name a file or directory with an embedded space (Program Files). Using [[ ]] rather than single brackets reduces the need, but does not remove it altogether.
Always declare variables inside functions as local, unless you really need to use a global (which you usually don't). If you don't do that then the variables inside a function could stomp on those outside, particularly if you reuse the function in several scripts.
The if statement after calling the function is incorrect. You are testing for true/false (which it won't be) and you have omitted a $ prefix.
promptFile()
{
local file
while true
do
read -p "Please provide a full path [q to quit]: " file
if [ "$file" == q ]; then
echo "Exiting.." >&2
return 1
fi
if [ ! -f "$file" ]; then
echo "File does not exist, please try again" >&2
else
echo "$file"
break
fi
done
}
tempLoc=$(promptFile)
if [ -n "$tempLoc" ]; then
fileLocation=$tempLoc
fi

Related

UNIX : How to move all file inside folder without getting binary operator expected

I have bash unix script to move all files from folder to another folder.
But, it's always give me binary operation expected when there's more than 1 file in source folder.
My Code goes like this
#Variable
NAS_MYTH=/cd/myth_extfile/shyn/
NAS_SHYN=/cd/shyn/MYTH/*
#Script
echo " Move file SHYN to NAS MYTH : $NAS_MYTH"
if [ -f $NAS_SHYN ]; then
mv $NAS_SHYN $NAS_MYTH
echo "Success: moving files to $NAS_MYTH"
else
echo "Success : No files need to be moved"
fi
I'm using if method to check if there is data there, so it'll success error shown
But, this code i use is fine when in folder MYTH there is just one data, but when it's more than one data i'll get error output like this
Move file SHYN to NAS MYTH : /cd/myth_extfile/shyn/
./mv_shyn_files.sh: line 52: [: /cd/shyn/MYTH/FAQ - MYTH.pdf: binary operator expected
Sucess : No file need to be move
Can anyone help me? Thanks.
UPDATE
It's all solved, im using this code now
shopt -s nullglob
NAS_MYTH=/cd/myth_extfile/shyn/
NAS_SHYN=(/cd/shyn/MYTH/*)
echo "Checking if there is a file, please wait"
if [ "${#NAS_SHYN[#]}" != 0 ]; then
echo "There is ${#NAS_SHYN[#]} file"
mv -f "${NAS_SHYN[#]}" "$NAS_MYTH"
echo "Success : ${#NAS_SHYN[#]} file moved"
else
echo "Success : There is no file need to be moved"
fi
Thanks guys for helping me :D
NAS_SHYN=/cd/shyn/MYTH/* assigns a literal * to NASH_SHYN. But when you write $NAS_SHYN without quotes then the * is expanded into a list of files which are then expanended into a list of words by splitting the file names at whitespace. In your case, it seems like there is only one file, namely FAQ - MYTH.pdf. However, that file has spaces in it, therefore bash splits your filename into multiple words causing the error.
Instead, use an array and quote your variables.
shopt -s nullglob
NAS_SHYN=(/cd/shyn/MYTH/*)
if [ "${#NAS_SHYN[#]}" != 1 ]; then
echo "expected one match but found ${#NAS_SHYN[#]}"
exit 1
fi
echo "The only match is ${#NAS_SHYN[0]}"
if [ -f "${NAS_SHYN[0]}" ]; then
…
By the way ALLCAPS variables are by convention special or environment variables. To avoid accidental name collisions use lowercase variables instead.

Unexpected end of file in while loop in bash

I am trying to write a bash script that will do the following:
Take a directory or file as input (will always begin with /mnt/user/)
Search other mount points for same file or directory (will always begin with /mnt/diskx)
Return value
So, for example, the input will be "/mnt/user/my_files/file.txt". It will search if ""/mnt/disk1/my_files/file.txt" exists and will incrementally look for each disk (disk2, disk3, etc) until it finds it or disk20.
This is what I have so far:
#/user/bin/bash
var=$1
i=0
while [ -e $check_var = echo $var | sed 's:/mnt/user:/mnt/disk$i+1:']
do
final=$check_var
done
It's incomplete yes, but I am not that proficient in bash so I'm doing a little at a time. I'm sure my command won't work properly yet either but right now I am getting an "unexpected end of file" and I can't figure out why.
There are many issues here:
If this is the actual code you're getting "unexpected end of file" on, you should save the file in Unix format, not DOS format.
The shebang should be #!/usr/bin/bash or #!/bin/bash depending on your system
You have to assign check_var before running [ .. ] on it.
You have to use $(..) to expand a command
Variables like $i are not expanded in single quotes
sed can't add numbers
i is never incremented
the loop logic is inverted, it should loop until it matches and not while it matches.
You'd want to assign final after -- not in -- the loop.
Consider doing it in even smaller pieces, it's easier to debug e.g. the single statement sed 's:/mnt/user:/mnt/disk$i+1:' than your entire while loop.
Here's a more canonical way of doing it:
#!/bin/bash
var="${1#/mnt/user/}"
for file in /mnt/disk{1..20}/"$var"
do
[[ -e "$file" ]] && final="$file" && break
done
if [[ $final ]]
then
echo "It exists at $final"
else
echo "It doesn't exist anywhere"
fi

Bash Script To Check for Duplicate File Names from Command Line Arguments

I'm working on a shell script that will copy all the files from the command line to a directory. If the command line arguments contain any duplicate files, I want to prompt the user to either overwrite the existing file (it should now be in the directory), don't copy it, or rename it and then copy it.
What is the best way to approach this problem?
Here's some pseudocode of what I'm thinking:
for var in "$#"
do
for file in "$dirName" #unsure about this syntax too
do
if [ file or directory with name "$fileName" exists]; then
prompt user with options #i can handle this part :)
else
mv $var $dirName
fi
done
done
You are overcomplicating things.
for var in "$#"
do
if [ -e "$dirname/$var" ]; then
prompt user with options #i can handle this part :)
else
mv "$var" "$dirName"
fi
done
Make sure you use adequate quoting everywhere, by the way. Variables which contain file names should basically always be double quoted.

Bash script is not asking for user input?

I have this bash code:
#!/bin/bash
ls "$SOURCE" > cplist.txt
cat cplist.txt | while read FILE; do
echo "Would you like to copy $FILE? y/n"
read ANSWER
if [ "$ANSWER" = "y" ]; then
cp -v "$FILE" ./Destination;
else
echo "File NOT coppied"
fi
done
The idea is to make a list of all files in a folder, then ask the user each file they want to copy to the destination folder. Currently this script wont allow user input after: "would you like to copy $file? y/n". I cant work out why as I'm very new to this kind of thing. Any ideas?
Thanks in advance!
You've run into problems as you're using two separate calls to read but you're making things more complicated than they need to be. Try something like this:
#!/bin/bash
for file in "$source"/*; do
echo "Would you like to copy $file? y/n"
read -r answer
if [ "$answer" = "y" ]; then
cp -v "$file" ./Destination;
else
echo "File NOT copied"
fi
done
Instead of parsing ls (which is generally considered a cardinal sin), this uses the shell's built-in glob expansion to match everything in the directory $source. I have added the -r switch to read, which is a good idea in general. I have also made your variable names lowercase. Uppercase variables should be reserved for built-in variables such as $PATH, which you certainly don't want to ever accidentally overwrite.
You make the cplist.txt to the remaining part's standard input where the read tries to read from.

Search for specific extension files (shellscript)

I am new to shell scripting, I have this script:
#!/bin/bash
path_file_conf=/fullpath/directory/*.conf
if [ -e "$path_file_conf" ];then
echo "Found file"
else
echo "No found file"
fi
The result is always "No found file" even if I have a .conf files inside /fullpath/directory/ folder.
May I know what part of the code is wrong?
Thanks in advance!
I would try something like this:
for filename in /fullpath/directory/*.conf
do
if [ -e "$filename" ] # If finds match...
then
echo "Found file"
echo
else
echo "No found file"
fi
done
I haven't tested so I'm not certain it works, but it will at least give you the overall strategy.
The expression:
path_file_conf=/fullpath/directory/*.conf
May have multiple path names that match. So the value of $path_file_conf may end up being, for example:
/fullpath/directory/foo1.conf /fullpath/directory/foo2.conf
The conditional:
if [ -e "$path_file_conf" ]; then
Checks for the existence of a single file. If "/fullpath/directory/foo1.conf /fullpath/directory/foo2.conf" doesn't name a "single file", which it won't, then the condition will fail even though the files exist.
You could check this way. If the path doesn't expand, it will fail and exit. If it finds at least one good path, it will succeed and exit.
for pf in $path_file_conf ; do
if [ -e "$pf" ] ; then
echo "Found"
break
else
echo "Not found"
fi
done
The line causing trouble is:
path_file_conf=/full/path/directory/*.conf
The shell does not do wild-card expansion on the name when there are multiple files to match, or when no files match, so (except in the unusual circumstance of having a file called *.conf with an asterisk) the -e test fails. There is probably an option in bash to generate an error when a wild card fails to match; I would never use it.
You can use:
path_file_conf=( /full/path/directory/*.conf )
This gives you an array with the names of the files as the elements of the array. However, if there are no files that match, it gives you the name as written as the only element of the array.
From there, you can check each file in turn:
for conf_file in "${path_file_conf[#]}"
do
if [ -e "$conf_file" ]
then echo "Found file $conf_file"
else echo "No such file as $conf_file"
fi
done
You can determine the number of names with ${#path_file_conf[#]}, but remember that 1 could indicate a real file or a non-existent file.

Resources