Why doesn't my if statement with backticks work properly? - bash

I am trying to make a Bash script where the user will be able to copy a file, and see if it was successfully done or not. But every time the copy is done, properly or not, the second output "copy was not done" is shown. Any idea how to solve this?
if [ `cp -i $files $destination` ];then
echo "Copy successful."
else
echo "Copy was not done"
fi

What you want is
if cp -i "$file" "$destination"; then #...
Don't forget the quotes.
You version:
if [ `cp -i $files $destination` ];then #..
will always execute the else branch.
The if statement in the shell takes a command.
If that command succeeds (returns 0, which gets assigned into $?), then the condition succeeds.
If you do if [ ... ]; then, then it's the same as
if test ... ; then because [ ] is syntactic sugar for the test command/builtin.
In your case, you're passing the result of the stdout* of the cp operation as an argument to test
The stdout of a cp operation will be empty (cp generally only outputs errors and those go to stderr). A test invocation with an empty argument list is an error. The error results in a nonzero exit status and thus you always get the else branch.
*the $() process substitution or the backtick process substitution slurp the stdout of the command they run

With back ticks you are testing the output of the cp command, not its status. You also don't need the test command (square brackets) here.
Just use:
if cp ... ; then
...

In addition to testing the output verses status as correctly pointed out in the other answer, you can make use of a compound command to do exactly what your are attempting, without requiring the full if ... then ... else ... fi syntax. For example:
cp -i "$files" "$destination" && echo "Copy successful." || echo "Copy was not done"
Which essentially does the exact same thing as the if syntax. Basically:
command && 'next cmd if 1st succeeded'
and
command || 'next cmd if 1st failed'
You are simply using command && 'next cmd if 1st succeeded' as the command in command || 'next cmd if 1st failed'. Together it is simply:
command && 'next cmd if 1st succeeded' || 'next cmd if 1st failed'
Note: make sure to always quote your variables to prevent word-splitting, and pathname expansion, etc...

Try:
cp -i $files $destination
#check return value $? if cp command was successful
if [ "$?" == "0" ];then
echo "Copy successful."
else
echo "Copy was not done"
fi

Related

Unix shell script to check if a file or directory with same name exists

I want to check if a file or directory with same name exists.
Is there any operator (expect -e) to check the file or directory, I dont want to add any extra condition.
Following code works fine to check the existence of a file:
#!/bin/bash
if [ -e /path/to/the/file/sample ]
then
echo "ok"
else
echo "nok"
fi
Note that sample can be a file or a directory.
In my tests with bash 4.2 , the condition [ -e /path/to/the/file/sample ] works for BOTH files and directories.
As an alternative you can :
Either to use another operator like -d according to bash man page.
if [ -e /path/to/the/file/sample ] || [ -d /path/to/the/file/sample ];then echo "ok"; else echo "nok";fi
Or to use the exit code of a command like ls.
if ls "/path/to/the/file/sample" &>/dev/null ; then echo "is here";else echo "not here";fi
If you try to perform ls on an existed file or directory ls will return 0, otherwise will return a non-zero value (returns 2 in my tests for non-existed files or directories).
Quoting is necessary to handle correct names that may include spaces.

bash test for existence of files on SFTP server

The issue that I have is with the line: "does not work" - below. The last line does indeed work - but I need to understand why the second to last line does not. I need to check for file existence on the remote server.
Have a need to check for existence for files at the following location:
/home/remoteuser/files/
and when the files are processed, they are moved to:
/home/remoteuser/logs/archive/
Would like to create an alert if the files exist at - in other words, the files were not processed:
/home/remoteuser/logs/
Found the following page and seems to be what I am looking for:
http://www.cyberciti.biz/tips/find-out-if-file-exists-with-conditional-expressions.html
Testing this and I know there are files there, but does not work:
ssh remoteuser#1.2.3.4 [ ! -f /home/remoteuser/logs/archive/*.* ] && echo "File does not exist in the root" >> /home/localuser/files/dirlist.txt
Because we know this works and does indeed list files on the local server:
ssh remoteuser#1.2.3.4 ls -l /home/remoteuser/logs/archive/*.* >> /home/localuser/files/dirlist.txt
Wildcards and test construct in Bash
You cannot use the wildcards in the [ command to test the existence of multiple files. In fact, the wildcards will be expanded and all the files will be passed to the test. Te results is that it would complain that "-f" is given too many arguments.
Try this in any non empty directory to see the output:
[ ! -f *.* ]
The only situation in which the above command does not fail is when there is only one file matching the expression, in your case a non hidden file of the form "*.*" in /home/remoteuser/logs/archive/
Using Find
A possible solution is to use find in combination with grep:
ssh name#server find /path/to/the/files -type f -name "\*.\*" 2>/dev/null | grep -q . && echo "Not Empty" || echo "Empty"
find search for regular files (-type f) whose names are in the form . (-name) and return false if nothing is found, then "grep -q ." return 1 or 0 if something is found or not.
Your goal can be accomplished with only shell builtins -- and without any uses of those builtins which depend on their behavior when passed invalid syntax (as the [ ! -e *.* ] approach does). This removes the dependency on having an accessible, working find command on your remote system.
Consider:
rmtfunc() {
set -- /home/remoteuser/logs/*.* # put contents of directory into $# array
for arg; do # ...for each item in that array...
[ -f "$arg" ] && exit 0 # ...if it's a file that exists, success
done
exit 1 # if nothing matched above, failure
}
# emit text that defines that function into the ssh command, then run same
if ssh remoteuser#host "$(declare -f rmtfunc); rmtfunc"; then
echo "Found remote logfiles"
else
echo "No remote logfiles exist"
fi
ANSWER:
Did find the following about the use of -e for a regular file.
http://www.cyberciti.biz/faq/unix-linux-test-existence-of-file-in-bash/
Even though it says "too many arguments" it does seem to test out OK.
ssh remoteuser#1.2.3.4 [ ! -e /home/remoteuser/logs/archive/*.zip ] && echo "File does not exists in the root" >> /home/localuser/files/dirlist.txt || echo "File does exists in the root" >> /home/localuser/files/dirlist.txt
Your script will work simply using double parenthesis:
ssh remoteuser#1.2.3.4 [[ ! -f /home/remoteuser/logs/archive/*.* ]] && echo "File does not exist in the root" >> /home/localuser/files/dirlist.txt
From man bash
Word splitting and pathname expansion are not performed on the words between the [[ and ]].

Bash + check for file exist with a path home ~

I haven't found anything to deal with this particular situation. Maybe there is a easy way that I'm overlooking instead of checking for a string to catch this scenario. When I check an input for existence of a file, if the input is ~/filecheck , this won't work. I get negative results while the file is in my home folder. Any suggestions for improvement to any part of the script I will definitely appreciate. I also have to use an input instead of a argument. Thanks for any help.
my test script
read -p "Enter: " input
echo $input
if [ -f $input ]; then
read -p "Do you REALLY want to delete this file?:" input2
if [[ $input2='y' || $input2 = 'Y' ]]
then
rm -f $input
elif [[ $input2='n' || $input2='N' ]]
then
exit
else
echo "Invaild Option"
exit
fi
else
echo Invaild Option!
exit
fi
Since you are entering input string as ~/filecheck shell doesn't expand tilde while using condition with -f in [ -f $input ]
You can use it this way but it is not recommended and potentially dangerous as arbitrary commands can be run by user:
if [[ -f $(bash -c "echo $input") ]]; then
echo "file exists"
fi
EDIT: As per the comments below to avoid risky bash -c you can use:
if [[ -f "${input/\~/$HOME}" ]]; then
echo "file exists"
fi
You can't have tilde expansion in this part of the program without using something based on eval—and you don't want to do that with user input. So, your poor-man solution will be to substitute any potential leading ~/ with the expansion of $HOME/. Here's the adaptation of your script in an arguably better style:
#!/bin/bash
read -e -p "Enter: " input
input=${input/#~\//$HOME/} # <--- this is the main idea of this answer (and it's rather poor)
echo "$input"
if [[ -f $input ]]; then
read -e -p "Do you REALLY want to delete this file? " input2
if [[ ${input2,,} = y ]]; then
rm -f -- "$input"
elif [[ ${input2,,} = n ]]; then
exit
else
echo "Invalid Option"
exit
fi
else
echo "Invalid Option!"
fi
exit
Now, out of curiosity, why are you spending time to make a wrapper around rm? you're making a clunky interface to an already existing program, without adding anything to it, only rendering it less powerful and less easy to use.
If all what you want it's to ask the user before deleting, you can use:
rm -i
This will give you appropriate error in the case file does not exist.

Unix while loop to test command line argument if it is a directory

Trying to make a script that will take a command line argument as a pathname and then test if it is a working directory. Then I wish to run commands (tests) on the directory such as how many files in what sub directories etc.
I am unable to think of a logic to test this with. How would you determine if the argument is a directory?
This is what I have tried
if [ -d "$1" ]
then
echo "It works"
fi
I dont get "It works" when I try this. So I switched it to -a for a file because it is easier to test. And again, I do not get the echo output.
Use the -d option to the test command.
if [ -d "$1" ]
then ...
fi
The title mentions a while loop, but none of the previous commentary seems to mention that. You might try a simple script like:
#!/bin/sh
for arg; do
if test -d "$arg";
echo "$arg is a directory"
else
echo "$arg is not a directory"
fi
done
For variety, you could rewrite that as:
#!/bin/sh
for arg; do
not=$(test -d "$arg" || echo "NOT ")
echo "$arg is ${not}a directory"
done

Bash script - Nested If Statement for If File Doesn't Exist

I'm trying to compile a script that will read user input, and check if the file after the y/n statement. Then it will make files executable. I think the problem with my script is conditional ordering but check it out yourself:
target=/home/user/bin/
cd $target
read -p "This will make the command executable. Are you sure? (y/n)" CONT
if [ "$CONT" == "y" ];
then
chmod +x $1
echo "File $1 is now executable."
else
if [ "$(ls -A /home/user/bin/)" ];
then
echo "File not found."
else
echo "Terminating..."
fi
fi
As I said, I need the script to scan for the file after the y/n statement is printed. The script works fine how it is but still gives the "file is now executable" even if the argument file doesn't exist (but just gives the standard system "cannot find file" message after the echo'd text).
Your script is mostly correct, you just need to check if the file exists first. Also, it's not the best practice to use cd in shell scripts and not needed here.
So re-writing it
#!/bin/bash
target="/home/user/bin/$1"
if [[ ! -f $target ]]; then
echo "File not found."
else
read -p "This will make the command executable. Are you sure? (y/n) " CONT
if [[ $CONT == "y" ]]; then
chmod +x "$target"
echo "File $1 is now executable."
else
echo "Terminating..."
fi
fi
To get an understanding:
Your script will take one argument (a name of a file).
You ask if you want to make that file executable.
If the answer is 'yes', you make the file executable.
Otherwise, you don't.
You want to verify that the file exists too?
I'm trying to understand your logic. What does this:
if [ "$(ls -A /home/user/bin/)" ];
suppose to do. The [ ... ] syntax is a test. And, it has to be one of the valid tests you see here. For example, There's a test:
-e file: True if file exists.
That mean, I can see if your file is under /home/user/bin:
target="/home/user/bin"
if [ -e "$target/$file" ] # The "-e" test for existence
then
echo "Hey! $file exists in the $target directory. I can make it executable."
else
echo "Sorry, $file is not in the $target directory. Can't touch it."
fi
Your $(ls -A /home/user/bin/) will produce a file listing. It's not a valid test like -e unless it just so happens that the first file in your listing is something like -e or -d.
Try to clarify what you want to do. I think this is something more along the lines you want:
#! /bin/bash
target="/home/user/bin"
if [ -z "$1" ] # Did the user give you a parameter
then
echo "No file name given"
exit 2
fi
# File given, see if it exists in $target directory
if [ ! -e "$target/$1" ]
then
echo "File '$target/$1' does not exist."
exit 2
fi
# File was given and exists in the $target directory
read -p"Do you want $target/$1 to be executable? (y/n)" continue
if [ "y" = "$continue" ]
then
chmod +x "$target/$1"
fi
Note how I'm using the testing, and if the testing fails, I simply exit the program. This way, I don't have to keep embedding if/then statements in if/then statements.

Resources