verifing some arguments in bash - linux

I'm doing a verification in BASH
if [ !( ( -e $f ) || ($# -lt 2) || ( -d $f ) ) ]; then
exit 0
fi
I'm trying to see if the file existes or if it's a directory or enough args are passed via terminal. Can I do this or must it be done in another way?

I gather from your question you want to reject anything that is not an ordinary file. In that case, the test for a directory is redundant if you use -f instead of -e.
if [[ ! -f "$f" || $# -lt 2 ]]; then
exit 0
fi
By the way, if this is an error exit, you should exit with something other than 0 - 0 indicates success in most cases; exit 1 would be better.

It looks like you have at least a few problems here.
The syntax for your test will require the more modern double [[ command instead of the legacy single [ command.
You're missing a fi at the end. (I edited this one in for you, with thanks to whoever modded it up.)
You may have the sense of your test reversed, but I'm not sure. It depends on what the rest of your script looks like.

Related

Shell Scripting to Compress directory [duplicate]

This question already has an answer here:
Shell spacing in square brackets [duplicate]
(1 answer)
Closed 4 years ago.
$1 is file / folder that want to compressed
Output filename is the same name, plus current date and ext
if output name exist, then just give warning
Example:
./cmp.sh /home/user
It will be /home/user to /home/user_2018-03-11.tar.bz2
i already have lead, but i'm stuck
#!/bin/bash
if ["$1" == ""]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
if [[ -f "$1" || -d "$1" ]]; then
tar -cvjSf $1"_"$(date '+%d-%m-%y').tar.bz2 $1
fi
but the output is _22-04-2018.tar.bz2
I see that you're using quotes to avoid the problem the underscore getting used as part of the variable name. So while $1 is a positional paramater, $1_ is a variable that you have not set in your script. You can avoid this issue by using curly braces, like ${1}. Anything inside the braces is part of the variable name, so ${1}_ works. This notation would be preferable to $1"_" which leaves a user-provided variable outside of quotes. (Of course, "$1"_ would do the job as wel.)
Also, it's probably safer to set the filename in a variable, then use that for all your needs:
#!/bin/bash
if [ -z "$1" ]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
filename="${1}_$(date '+%F').tar.bz2"
if [ -e "$filename" ]; then
echo "WARNING: file exists: $filename" >&2
else
tar -cvjSf "$filename" "$#"
fi
Changes:
you need spaces around your square brackets in an if condition,
while you can test for equivalence to a null string, -z is cleaner, though you could also test for [ $# -eq 0 ], counting the parameters provided,
using $filename makes sure that your test and your tar will always use the same name, even if the script runs over midnight, and is way more readable,
variables should always be quoted.
Also, are you sure about the -S option for tar? On my system, that option extracts sparse files, and is only useful in conjunction with -x.
ALSO, I should note that as I've rewritten it, there's nothing in this script which is specific to bash, and it should be portable to POSIX shells as well (ash/dash/etc). Bash is great, but it's not universal, and if through your learning journey you can learn both, it will give you useful skills across multiple operating systems and environments.
Use -z switch to check if blank
#!/bin/bash
if [[ -z "$1" ]]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
if [[ -f "$1" || -d "$1" ]]; then
tar -cvjSf $1"_"$(date '+%d-%m-%y').tar.bz2 $1
fi

Arrays in Shell Script, not Bash

I am probably just having a brain fart, but I can not for the life of me figure out how to loop through an array in shell script, not bash. Im sure the answer is on stackoverflow somewhere already, but I can not find a method of doing so without using bash. For my embedded target system bash is not currently an option. Here is an example of what I am attempting to do and the error that is returned.
#!/bin/sh
enable0=1
enable1=1
port=0
while [ ${port} -lt 2 ]; do
if [ ${enable${port}} -eq 1 ]
then
# do some stuff
fi
port=$((port + 1))
done
Whenever I run this script the error "Bad substitution" is returned for line with the if statement. If you guys have any ideas I would greatly appreciate it. Thanks!
a="abc 123 def"
set -- $a
while [ -n "$1" ]; do
echo $1
shift
done
Output via busybox 1.27.2 ash:
abc
123
def
BusyBox provides ash which does not directly provide array support. You could use eval and something like,
#!/bin/busybox sh
enable0=0
enable1=1
for index in 0 1 ; do
eval assign="\$enable$index"
if [ $assign == 1 ]; then
echo "enable$index is enabled"
else
echo "enable$index is disabled"
fi
done
One could use positional parameters for that...
http://pubs.opengroup.org/onlinepubs/009696799/utilities/set.html
#!/bin/sh
enable0=0
enable1=1
set -- $enable0 $enable1
for index in 0 1; do
[ "$1" -eq 1 ] && echo "$1 is enabled." || echo "$1 is disabled."
shift
done
Running on busybox:
~ $ ./test.sh
0 is disabled.
1 is enabled.
It's best not to use eval unless there is no other alternative. (The recent spate of bash exploits is due to the shell internally evaling the contents of environment variables without verifying their contents first). In this case, you seem to be in complete control for the variables involved, but you can iterate over the variable values without using eval.
#!/bin/sh
enable0=1
enable1=1
for port_enabled in "$enable0" "$enable1"; do
if [ "$port_enabled" -eq 1 ]; then
# do some stuff
fi
done

Issue controlling script flow

I'm new to shell scripting, my script appears to be okay, but its the flow that I'm having an issue controlling. Could someone point out what silly mistake I've made please.
#! /bin/sh
echo "Are you sure youx want to delete $1? Answer y or n"
read ans
echo $ans
if $ans = "y"|"Y"
then
mv $1 /home/parallels/dustbin
echo "File $1 has been deleted"
else echo "File $1 has not been deleted"
fi
Make your if condition like this:
if [ "$ans" = "y" -o "$ans" = "Y" ]
There are a few things wrong with your script. Some are serious, some are less so.
First, the serious problems.
As guru suggested, you need to use square brackets to surround your if condition. This is because if only tests for the output of a condition, it doesn't perform actual string comparisons. Traditionally, a program called /bin/test, which was also called /bin/[ took care of that. These days, that functionality is built in to the shell, but /bin/sh still behaves as if it's a separate program.
In fact, you can do interesting things with if when you don't use square brackets for your condition. For example, if grep -q 'RE' /path/to/file; then is quite common. The grep -q command issues no output, but simply returns a "success" or "fail" that is detected by if.
Second serious problem is that you are echoing a status that may or may not be true. I call this a serious problem because ... well, log messages simply shouldn't make false claims. If the permissions are wrong for the file in $1, or the filename contains a space, then your mv command will fail, but the message will claim that it did not. More on this later.
Next, the less serious problems.
These are mostly style and optimization things.
First off, read on most platforms includes a -p option that lets you specify a prompt. Use this, and you don't need to include an echo command.
Second, your indenting makes it hard to see what the if construct is wrapping. This isn't a huge problem in a program this small, but as you grow, you REALLY want to follow consistent standards.
Third, you can probably get more flexibility in multiple-choice questions like this if you use case statements instead of if.
After all that, here's how I'd write this script:
#!/bin/sh
if [ "$1" = "-y" ]; then
ans=y
shift
elif [ -t 0 ]; then
read -p "Are you sure you want to delete '$1' (y/N) ? " ans
fi
case "$ans" in
Y*|y*)
retval=0
if [ -z "$1" ]; then
retval=64
echo "ERROR: you didn't specify a filename." >&2
if [ ! -f "$1" ]; then
retval=66
echo "ERROR: file '$1' not found!" >&2
elif mv "$1" /home/parallels/dustbin/; then
echo "File '$1' has been deleted" >&2
else
retval=$?
echo "ERROR: file '$1' could not be deleted!" >&2
fi
;;
*)
echo "ABORT: file '$1' has not been deleted" >&2
retval=4
;;
esac
exit $retval
Aside from what's mentioned above, here are some things in this code snippet:
[ "$1" = "-y" ] - if the user specifies a -y option, then we behave as if the question was answered with a "yes".
[ -t 0 ] - this tests whether we are on an interactive terminal. If we are, then it makes sense to ask questions with read.
Y*|y*) - in a case statement, this matches any string that begins with an upper or lower case "y". Valid affirmative responses would therefore be "Y", "yes", "yellow", etc.
[ ! -f "$1" ] - this tests whether the file exists. You can man test or man sh to see the various tests available in shell. (-f may not be the most appropriate for you.)
>&2 - at the end of a line, sends its output to "standard error" instead of "standard out". This changes how output will be handled by pipes, cron, etc. Errors and log data are often sent to stderr, so that stdout can be dedicated to a program's actual output.
mv "$1" ... - The filename is in quotes. This protects you in case the filename has special characters like spaces in it.
$retval - the values for this came from a best guess of the closest item in man sysexits.
retval=$? - this is the exit status of the most recently executed command. In this case, that means we're assigning mv's exit status to the variable $retval, so that if mv failed, the whole script reports the reason for the fail, as far as mv is concerned.
You can also convert the user response to either case and just check it for respective case like
read ans
ans=${ans,,} # make 'ans' lowercase, or use ${ans^^} for making it uppercase
if [ "$ans" = "y" ]
then
....
fi
Below is the perfect code with error handling included
#!/bin/sh
echo "Are you sure you want to delete $1? Answer y or n"
read ans
echo $ans
if [ $ans == "y" ] || [ $ans == "Y" ]
then
if [ -f $1 ]
then
mv $1 /home/parallels/dustbin
echo "File $1 has been deleted"
else
echo " File $1 is not found"
fi
else
echo "File $1 has not been deleted"
fi

Running a command only once from a script, even script is executed multiple times

I need some help. I'm having a script 'Script 1', which will call 'Script 2' to run in background which checks something periodically. But I want the Script 2 to get started only once, even Script 1 is called multiple times. Is there a way to do it?
It would be even more helpful, if someone suggests some commands to achieve this.
Thanks in advance
Sure, you can put something like this at the top of Script2:
if [[ -f /tmp/Script2HasRun ]] ; then
exit
fi
touch /tmp/Script2HasRun
That will stop Script2 from ever running again by using a sentinel file, unless the file is deleted of course, and it probably will be at some point since it's in /tmp.
So you probably want to put it somewhere else where it can be better protected.
If you don't want to stop it from ever running again, you need some mechanism to delete the sentinel file.
For example, if your intent is to only have one copy running at a time:
if [[ -f /tmp/Script2IsRunning ]] ; then
exit
fi
touch /tmp/Script2IsRunning
# Do whatever you have to do.
rm -f /tmp/Script2IsRunning
And keep in mind there's a race condition in there that could result in two copies running. There are ways to mitigate that as well by using the content as well as the existence, something like:
if [[ -f /tmp/Script2IsRunning ]] ; then
exit
fi
echo $$ >/tmp/Script2IsRunning
sleep 1
if [[ "$(cat /tmp/Script2IsRunning 2>/dev/null)" != $$ ]] ; then
exit
fi
# Do whatever you have to do.
rm -f /tmp/Script2IsRunning
There are more levels of protection beyond that but they become complex, and I usually find that suffices for most things.

Bash: Create a file if it does not exist, otherwise check to see if it is writeable

I have a bash program that will write to an output file. This file may or may not exist, but the script must check permissions and fail early. I can't find an elegant way to make this happen. Here's what I have tried.
set +e
touch $file
set -e
if [ $? -ne 0 ]; then exit;fi
I keep set -e on for this script so it fails if there is ever an error on any line. Is there an easier way to do the above script?
Why complicate things?
file=exists_and_writeable
if [ ! -e "$file" ] ; then
touch "$file"
fi
if [ ! -w "$file" ] ; then
echo cannot write to $file
exit 1
fi
Or, more concisely,
( [ -e "$file" ] || touch "$file" ) && [ ! -w "$file" ] && echo cannot write to $file && exit 1
Rather than check $? on a different line, check the return value immediately like this:
touch file || exit
As long as your umask doesn't restrict the write bit from being set, you can just rely on the return value of touch
You can use -w to check if a file is writable (search for it in the bash man page).
if [[ ! -w $file ]]; then exit; fi
Why must the script fail early? By separating the writable test and the file open() you introduce a race condition. Instead, why not try to open (truncate/append) the file for writing, and deal with the error if it occurs? Something like:
$ echo foo > output.txt
$ if [ $? -ne 0 ]; then die("Couldn't echo foo")
As others mention, the "noclobber" option might be useful if you want to avoid overwriting existing files.
Open the file for writing. In the shell, this is done with an output redirection. You can redirect the shell's standard output by putting the redirection on the exec built-in with no argument.
set -e
exec >shell.out # exit if shell.out can't be opened
echo "This will appear in shell.out"
Make sure you haven't set the noclobber option (which is useful interactively but often unusable in scripts). Use > if you want to truncate the file if it exists, and >> if you want to append instead.
If you only want to test permissions, you can run : >foo.out to create the file (or truncate it if it exists).
If you only want some commands to write to the file, open it on some other descriptor, then redirect as needed.
set -e
exec 3>foo.out
echo "This will appear on the standard output"
echo >&3 "This will appear in foo.out"
echo "This will appear both on standard output and in foo.out" | tee /dev/fd/3
(/dev/fd is not supported everywhere; it's available at least on Linux, *BSD, Solaris and Cygwin.)

Resources