Can anyone tell me why this is always saying that the directory is not writable, when it absolutely is?
$dnam="/home/bryan/renametest/C D"
# Is the directory writable
err=0
if [ ! -w $dnam ]
then
# Not writable. Pop the error and exit.
echo "Directory $dnam is not writable"
err=1
fi
You need double-quotes around $dnam -- without them, it's interpreted as two separate shell words, "/home/bryan/renametest/C" and "D", which makes an invalid test expression and hence fails. This should work:
if [ ! -w "$dnam" ]
#tink's suggestion of [[ ]] is a cleaner way of doing tests like this, but is only available in bash (and some other shells with extended syntax). The fact that you get [[: not found means you're using a fairly basic shell, not bash.
I see multiple problems:
You are using a space inside your variable. This is not illegal, but in combination line you use the variable unescaped and generate the following command:
if [ ! -w /home/bryan/renametest/C D ]
This is not a valid syntax. The simplest way to fix this is changing the line to
if [ ! -w "$dnam" ]
The next problem is worse: On my system, help test returns the text:
-w FILE True if the file is writable by you.
Which means, the command doesn't support directories but only files. If you want to check if a directory is writable, you will have to use a different command
As everyone else said, the $dnam variable needs double quotes. Here's why:
The [ ... ] is an alias to the test command. If you look in your system, you will see a file called /bin/[ or maybe /bin/usr/[. On some systems, this is a hard link to /bin/test or /bin/usr/test. The if statement executes what comes after the if, and if that command returns a zero exit status, the if statement will execute the then clause. Otherwise, if there is an else clause, that will execute instead.
To allow for boolean testing, Unix included the test command, so you could do this:
if test -d "$directory"
then
echo "Directory $directory exists!"
fi
Later on, the /bin/[ was added as syntactic sugar. This is identical to the above:
if [ -d "$directory" ]
then
echo "Directory $directory exists!"
fi
Now, both [ and test are builtin commands, but they are *still commands. This means that the shell interpolates the command and then executes it.
Try executing the following:
$ set -xv # Turns on shell debugging
$ dnam="/home/bryan/renametest/C D"
dnam="/home/bryan/renametest/C D"
+ dnam='/home/bryan/renametest/C D'
$ test -d $dnam
test -d $dnam
+ test -d /home/bryan/renametest/C D
$ echo $?
echo $?
+ echo 1
1
$ test -d "$dnam" # Now with quotes
test -d $dnam
+ test -d "/home/bryan/renametest/C D"
$ echo $?
echo $?
+ echo 0
0
$ set +xv # Turn off the debuggin
Each command is echoed twice. The first time as written, and the second time after the line is interpolated. As part of the interpolation, the shell splits parameters on white space. As you can see, the test command is testing the presence of /home/bryan/renamtest/C which doesn't exist and thus not writable. I'm actually surprised that the test command didn't print an error message because you passed it an extra parameter.
In the second attempt, you added quotes. These quotes prevented the shell from splitting your parameters on the space and keep the directory name as a single parameter.
Since [ ... ] is a command, you have to take into account the shell's interpolation of variables and other issues. And, if you're not absolutely careful, you can end up with errors.
Even worse, sometimes the [ ... ] might work and sometimes it might not. If your directory name didn't contain spaces, it will work as expected. Imagine you're writing a program, and you test it and everything works because all directories you've tried don't have spaces. Then, someone uses your program, but has a space in the directory. A substantial number of shell script bugs are do to this type of issue in if statements.
This is why Bash introduced the [[ ... ]] tests. The [[ isn't a command but a statement. This means that the shell doesn't directly interpolate the results. Instead, the parameters are parsed, and then any interpolation is done. Thus, this would have worked:
dnam="/home/bryan/renametest/C D" # No "$" in front of the variable!
# Is the directory writable
if [[ ! -w $dnam ]] # No quotation marks needed!
then
# Not writable. Pop the error and exit.
echo "Directory $dnam is not writable"
err=1
fi
It's almost always better to use the [[ ... ]] test rather than the [ ... ] test, so go ahead and get into the habit.
One more minor error, you had:
$dnam="/home/bryan/renametest/C D"
This gets interpolated by the shell, so the variable being set is whatever the value of $dnam just happens to be. If $dnam happened to equal "foo", you would been doing this:
foo="/home/bryan/renametest/C D"
Not what you want.
You want to leave the $ off when you set variables:
dnam="/home/bryan/renametest/C D"
Related
I have a script which write some warnings to separate file (it's name is passed as an argument). I want to make this script fail if there is a warning.
So, I need to pass some file name which must raise an error if someone try to write there.
I want to use some more or less idiomatic name (to include it to man page of my script).
So, let say my script is
# myScript.sh
echo "Hello" > $1
If I call it with
./myScript.sh /dev/stdin
it is not fail because /dev/stdin is not read-only device (surprisingly!)
After
./myScript.sh /
it is failed, as I want it (because / is a directory, you can't write there). But is is not idiomatic.
Is there some pretty way to do it?
if [ -w "$1" ]
then
echo "$Hello" > "$1" # Mind the double-quotes
fi
is what you're looking for. Below would even be better in case you've only
one argument.
if [ -w "$*" ]
then
echo "$Hello" > "$*" # Mind the double-quotes
fi
$* is used to accommodate nonstandard file names. "$*" combines multiple arguments into a single word. Check [ this ].
In a book I'm reading the below line
ls "$1" 2>/dev/null | grep "$1" 2>/dev/null 1>&2
when written in a script - by the book it says "The command is executed to check whether the file passed as the command line argument exists. The standard error is redirected to /dev/null (the unix black hole), and standard output is redirected to standard error by using 1>&2. Thus, the command does not produce any output or error message; its only puprose is to set the command returns status value $?."
But running the code:
if [ $? -eq 0 ]
would I not know it otherwise, I have tried without the cmd at beginning and with it as well with having no impact on the results. I'm sure the author would have written for some purpose. I cannot just figure what?
This looks like a very bad book, giving code that noone sane would ever write to poorly illustrate concepts that are generally used in completely different ways in shell scripts.
The line:
ls "$1" 2>/dev/null | grep "$1" 2>/dev/null 1>&2
is as described -- it has no visible effect other than setting the return code. Is your question about what this does in detail to get a return code or something else?
The line:
if [ $? -eq 0 ]
is an incomplete fragment that checks the return code of the previous command. It's incomplete as there is no then or fi, without which the shell will reject it as a syntax error and not do anything (if you type the above at a prompt, you'll get the secondary prompt, telling you the shell is waiting for more input to get a complete command). So without more code there's no apparent effect. Something more complete like:
if [ $? -eq 0 ]; then echo YES; else echo NO; fi
would output YES or NO based on that return code.
A more sensible way of doing the 6 lines starting with the ls would be:
if [ ! -e "$1" ]; then
echo "$1: not found"
exit 1
fi
As to what the ls line actually does, it runs ls (list files) with the name in $1 as an argument, then uses grep to search that listing for the same filename.
So if the file does not exist, ls gives an error and outputs nothing, so the grep fails (setting $? to 1). If the filename exists and is not a directory, the grep will succeed (setting $? to 0). Finally, if the filename exists and is a directory, it will search the contents of that directory, looking for any file or subdirectory with the same name as a substring -- which is probably just a bug. In addition, if $1 is a string beginning with -, it will do something fairly useless and unpredictable.
Overall, a prime example of a shell script that should never be written -- any student that turned in such a monstrosity should get an immediate F.
I need to check if a given string in a bash script is a command.
In other words: I need to check if that String is a filename in the /bin directory (Only /bin).
I tried
echo "Write a bash command: "
read -r var2
if [[ -z (find /bin -name $var2) ]]
then echo "That's not a command" && exit 1
fi
But it didn't work.
Ideas?
EDIT: Solved. As amdixon suggested I changed (find /bin -name $var2) for $(find /bin -name $var2).
Thanks dude.
Depending on your actual requirements, it can be easier than that:
if ! [ -x /bin/"$var2" ]
then
echo "That's not a command" && exit 1
fi
[ is short for the test command and with the -x argument, it will return 0 (true) if the given file is executable by you. Note that this will exclude commands that are executable by other users only because you have insufficient permissions.
If you use the -f argument instead, [ will test for any file in the /bin directory, whether it is executable or not (of course usually all of them are):
if ! [ -f /bin/"$var2" ]
then
echo "That's not a command" && exit 1
fi
If you need to make sure that the file is executable (even if it may not be executable by you), see this question for a solution using file.
Type help test on the command line to read more about possible arguments for [.
echo "Write a bash command: "
read -r var2
if [ ! -f /bin/"$var2" ]
then echo "That's not a command" && exit 1
fi
is the natural way to do this in bash. [ expr ] is the shorthand for the builtin test command. Type man builtins and scroll until you find test for a complete description of what testcan do for you. For instance, if you prefer testing simultaneously if the file exists and is executable, you can replace:
[ ! -f /bin/"$var2" ]
by:
[ ! -x /bin/"$var2" ]
I need to check if a given string in a bash script is a command.
vs
I need to check if that String is a filename in the /bin directory (Only /bin).
This is by no means the same. I guess you refer to well-known "shell commands" and in this case, There are three reasons you might be on the wrong path:
The root hierarchy is for everything needed to boot up the system. This might include what you consider "commands", but other stuff as well (like e.g. lvm or cryptsetup)
For the same reason, binaries you would consider "commands" might be missing from /bin, living in /usr/bin instead.
Shells have "builtin" commands and there is no guarantee you will find them as separate binaries at all.
Given all that, if you still want to look for executables in /bin, there is really no reason to use find at all. test (or the abbreviated version just writing brackets) will be enough, like if [ -x /bin/$var2 ]; then ...
I'm trying to make a shell script which should be used like this:
ocrscript.sh -from /home/kristoffer/test.png -to /home/kristoffer/test.txt
The script will then ocr convert the image file to a text file. Here is what I have come up with so far:
#!/bin/bash
export HOME=/home/kristoffer
/usr/local/bin/abbyyocr9 -rl Swedish -if ???fromvalue??? -of ???tovalue??? 2>&1
But I don't know how to get the -from and -to values. Any ideas on how to do it?
The arguments that you provide to a bashscript will appear in the variables $1 and $2 and $3 where the number refers to the argument. $0 is the command itself.
The arguments are seperated by spaces, so if you would provide the -from and -to in the command, they will end up in these variables too, so for this:
./ocrscript.sh -from /home/kristoffer/test.png -to /home/kristoffer/test.txt
You'll get:
$0 # ocrscript.sh
$1 # -from
$2 # /home/kristoffer/test.png
$3 # -to
$4 # /home/kristoffer/test.txt
It might be easier to omit the -from and the -to, like:
ocrscript.sh /home/kristoffer/test.png /home/kristoffer/test.txt
Then you'll have:
$1 # /home/kristoffer/test.png
$2 # /home/kristoffer/test.txt
The downside is that you'll have to supply it in the right order. There are libraries that can make it easier to parse named arguments on the command line, but usually for simple shell scripts you should just use the easy way, if it's no problem.
Then you can do:
/usr/local/bin/abbyyocr9 -rl Swedish -if "$1" -of "$2" 2>&1
The double quotes around the $1 and the $2 are not always necessary but are adviced, because some strings won't work if you don't put them between double quotes.
If you're not completely attached to using "from" and "to" as your option names, it's fairly easy to implement this using getopts:
while getopts f:t: opts; do
case ${opts} in
f) FROM_VAL=${OPTARG} ;;
t) TO_VAL=${OPTARG} ;;
esac
done
getopts is a program that processes command line arguments and conveniently parses them for you.
f:t: specifies that you're expecting 2 parameters that contain values (indicated by the colon). Something like f:t:v says that -v will only be interpreted as a flag.
opts is where the current parameter is stored. The case statement is where you will process this.
${OPTARG} contains the value following the parameter. ${FROM_VAL} for example will get the value /home/kristoffer/test.png if you ran your script like:
ocrscript.sh -f /home/kristoffer/test.png -t /home/kristoffer/test.txt
As the others are suggesting, if this is your first time writing bash scripts you should really read up on some basics. This was just a quick tutorial on how getopts works.
Use the variables "$1", "$2", "$3" and so on to access arguments. To access all of them you can use "$#", or to get the count of arguments $# (might be useful to check for too few or too many arguments).
I needed to make sure that my scripts are entirely portable between various machines, shells and even cygwin versions. Further, my colleagues who were the ones I had to write the scripts for, are programmers, so I ended up using this:
for ((i=1;i<=$#;i++));
do
if [ ${!i} = "-s" ]
then ((i++))
var1=${!i};
elif [ ${!i} = "-log" ];
then ((i++))
logFile=${!i};
elif [ ${!i} = "-x" ];
then ((i++))
var2=${!i};
elif [ ${!i} = "-p" ];
then ((i++))
var3=${!i};
elif [ ${!i} = "-b" ];
then ((i++))
var4=${!i};
elif [ ${!i} = "-l" ];
then ((i++))
var5=${!i};
elif [ ${!i} = "-a" ];
then ((i++))
var6=${!i};
fi
done;
Rationale: I included a launcher.sh script as well, since the whole operation had several steps which were quasi independent on each other (I'm saying "quasi", because even though each script could be run on its own, they were usually all run together), and in two days I found out, that about half of my colleagues, being programmers and all, were too good to be using the launcher file, follow the "usage", or read the HELP which was displayed every time they did something wrong and they were making a mess of the whole thing, running scripts with arguments in the wrong order and complaining that the scripts didn't work properly. Being the choleric I am I decided to overhaul all my scripts to make sure that they are colleague-proof. The code segment above was the first thing.
In bash $1 is the first argument passed to the script, $2 second and so on
/usr/local/bin/abbyyocr9 -rl Swedish -if "$1" -of "$2" 2>&1
So you can use:
./your_script.sh some_source_file.png destination_file.txt
Explanation on double quotes;
consider three scripts:
# foo.sh
bash bar.sh $1
# cat foo2.sh
bash bar.sh "$1"
# bar.sh
echo "1-$1" "2-$2"
Now invoke:
$ bash foo.sh "a b"
1-a 2-b
$ bash foo2.sh "a b"
1-a b 2-
When you invoke foo.sh "a b" then it invokes bar.sh a b (two arguments), and with foo2.sh "a b" it invokes bar.sh "a b" (1 argument). Always have in mind how parameters are passed and expaned in bash, it will save you a lot of headache.
#!/usr/dt/bin/dtksh
while getopts w:m: option
do
case $option in
w) wflag=1
wval="$OPTARG";;
m) mflag=1
mval="$OPTARG";;
?) printf 'BAD\n' $0
exit 2;;
esac
done
if [ ! -z "$wflag" ]; then
printf "W and -w arg is $wval\n"
fi
if [ ! -z "$mflag" ]; then
printf "M and -m arg is $mval\n"
fi
shift $(($OPTIND - 1))
printf "Remaining arguments are: $* \n"
at $wval <<ENDMARKER
echo $* >> Search_List
tr " " "\n" <Search_List >Usr_List
while true; do
if [ -s Usr_List ]; then
for i in $(cat Usr_List); do
if finger -m | grep $i; then
echo '$i is online' | elm user
sed '/$i/d' <Usr_List >tmplist
mv tmplist Usr_List
fi
done
else
break
fi
done
ENDMARKER
Essentially I want to keep searching through until it is empty. Each time an element of the list is found, it is deleted. Once the list is empty quit.
There are no error messages when I first run the command, it only shows up in an email containing the output of the at job.
Thanks in advance for any advice
EDIT: The script uses getopts and takes one argument for -w and one for -m, the w value is set as the time for the at job, the m still has to be used. Any arguments after the one for m are sent to a file called Search_List, Search_List is edited and saved as Usr_List. Then in the while loop, while Usr_List is not empty, the script checks the results of finger -m against the names in Usr_List. If a name is found, it is removed from Usr_List. Once Usr_List is empty, the program should stop.
elm is a way to send an email, so elm user sends an email to user.
The error is :
while: Expression syntax
at uses /bin/sh by default.
at now <<ENDMARKER
<code here>
ENDMARKER
All of this executes under /bin/sh, which on some systems can be Bourne Shell (Solaris for example).
You need to figure out what /bin/sh is for your system, then modify things accordingly. Plus, read the gurantees about what is and what is not in your "at" environment. I think the problem lies there. You have both UNIX and linux tags. So I cannot give a lot more help than that.
You can enable logging -- the way YOU need it -- of the at code chunk:
exec 2&>1 > /tmp/somefile.log
Then write debugging messages to stdout or stderr.
Your HEREDOC is being interpolated. Try quoting the delimiter:
at $wval << 'ENDMARKER'
Although ( I haven't looked closely) it appears that you want some interpolation. But you definitely do not want it on the line in which you reference $i, so quote that $ if you do not quote the entire heredoc:
if finger -m | grep \$i; then
You need to pass the -k option to at:
...
at -k $wval <<ENDMARKER
...
at is otherwise defaulting to your login shell which is csh or one of its derivatives.
It turns out that the while command and the if command needed to be combined.
while [[ -s Usr_List ]]; do
......
done