Risk of unquoted bash parameters/variables - linux

I recently read that it is dangerous to use unquoted parameter in bash scripts. I know that escaping is vital when it comes to SQL queries, but I don't see any problems with the following code (except for the obvious directory traversal)
#!/bin/bash
MYDIR="/tmp/$1"
if [ -d $MYDIR ] ; then
/bin/rmdir $MYDIR
fi
Are the quotes in the assignment line already enough?
The only thing that seems to be possible is that the "if" line says
# ./mm.sh "arg1 arg2"
./mm.sh: line 5: [: /tmp: binary operator expected
But that does not seem very dangerous to me. Is there anything I missed?
Edit: To be more specific. Is there any way I can elevate my privileges when I run this using sudo mm.sh?
Regards,
Lukas

rmdir refuses to remove non-empty directories, so it's unlikely that your script has any really catastrophic effects. Still you might try to figure out what happens when you call
./mm.sh ' -o -n /home/lukas/xyz'

ok, what if you have a directory /tmp/foo bar with space(s)?
if you pass argument in this way, without quote: ./mm.sh foo bar
your script will check /tmp/foo and if you really have a /tmp/foo, it would be removed, which is not what you want.
dangerous doesn't always mean something would be deleted unexpectedly. If you don't quote, your script will parse and use your parameter wrongly. It may stop running, like your example, and it may run, but incorrectly. Think about if your script generates an important report, but incorrectly. isn't it dangerous?

Related

What is ./lks.sh: Permission denied in Linux when running a shell script?

When I run lks.sh file in my system it show permission denied:
./lks.sh bash: ./lks.sh: Permission denied
What do I have to do to get this shell script to run?
This is my .sh file:
lokesh = "wait"
if[$lokesh == "wait"]
echo "$lokesh"
else
sudo shutdown -h now
Your script has a few issues.
First the “Permission Denied” is most likely because your script does not have execute rights which would allow the script to actually run. So you need to chmod it like this:
chmod 755 lks.sh
And then you should be able to run it. FWIW, the 7 and 755 gives you—the owner—execute, read & write permissions while the 5 gives group members and others execute & read permissions. Feel free to change that to 744 so you are the only one who can edit that script but others—via 4—can read it. Or even 700 so you are the only one who can ever do anything with that script.
But that said, your variable assignment for this seems off:
lokesh = "wait"
In my experience, there should be no spaces around the = like this:
lokesh="wait"
Next the spacing of this is syntactically incorrect:
if[$lokesh == "wait"]
It should be:
if [ $lokesh == "wait" ]
And finally your whole if/else syntax is incorrect; no then and no closing fi. So here is your final, cleaned up script:
lokesh="wait"
if [ $lokesh == "wait" ]; then
echo "$lokesh"
else
sudo shutdown -h now
fi
That said, the most immediate issue is the execute rights issue, but the other things will definitely choke your script as well.
You haven't made your script executable:
chmod +x lks.sh
Several issues with your shell script:
First as everyone has pointed out it requires executable bit to be turned on in order to run. You can do that by,
chmod a+x lks.sh
then running as you tried will work.
Now as #Giacomo1968 pointed out there are issues with your script:
First you should start all scripts with a shebang. This tells the operating system which interpreter to run for your script:
It should be the first line.
#!/bin/sh
Now that we've picked the interpreter to be Bourn Shell the rest is how to program what you wish correctly, I will list the correct code and explain it line by line
#!/bin/sh
okesh="wait"
if [ $lokesh = "wait" ]; then
echo "$lokesh"
else
sudo shutdown -h now
fi
White spaces matter in shell scripts
okesh="wait"
No spaces when specifying a shell variable, because the existance of = in the first token signifies to the shell that the statement is a variable declaration.
Otherwise if you do it like this:
okesh = "wait"
The shell will attempt to look for a program called okesh and if it finds it, execute it and pass = and "wait" as two command line arguments to it. Chances are you will get okesh: command not found error.
if[$lokesh=="wait"]
you need a space after if and a space between [ and $lokesh and another space between $lockesh and = and a space between = and "wait" and finally one between "wait" and ]
without all the spaces the shell thinks you are looking for a program named if[$lokesh=="wait"]
with first space it realizes you are starting an if block (which requires a closing fi at the very end)
if runs a program and looks at the return value, in this case that program happens to be test utility which has a synonym [. In most *nix systems, including linux, test has a symbolic link [ and when launched as '[' expects ']' as the last parameter. In bash (bourne again shell) that's found on most linux systems '[' is also a built-in function and acts the same way.
See test(1) man page for details on how [ works.
You are missing the mandatory then token, and the end fi token.
if is a built-in keyword. Use help if in bash to read how that works.

Bad substitution in bash script

I'm trying to get a script working to add swap space to a VPS, as a workaround a la this method. I thought I had it working but now, each time I get the error: fakeswap.sh: 5: Bad substitution whenever I try to execute it thusly: sudo sh fakeswap.sh.
Below is my code:
#!/bin/bash
SWAP="${1:-512}"
NEW="$[SWAP*1024]"; TEMP="${NEW//?/ }"; OLD="${TEMP:1}0"
umount /proc/meminfo 2> /dev/null
sed "/^Swap\(Total\|Free\):/s,$OLD,$NEW," /proc/meminfo > /etc/fake_meminfo
mount --bind /etc/fake_meminfo /proc/meminfo
free -m
Clearly the substitution that seems to be failing is on the line: NEW="$[SWAP*1024]"; TEMP="${NEW//?/ }"; OLD="${TEMP:1}0"
I'm somewhat ashamed to say that I don't REALLY understand what's supposed to happen on that line (apart from the fact that we seem to be declaring variables that are all derivatives of SWAP in one way or another). I gather that the lines below substitute new constants into a dummy configuration file (for lack of a better term) but I don't get how the variables TEMP and OLD are being defined.
Anyway, I was wondering if anyone might be able to see why this substitution isn't working...and maybe even help me understand what might be be happening when TEMP and OLD are defined?
Many thanks in advance!
sh is not bash. The sh shell doesn't recognize some valid bash substitutions.
The intention of that script is that it be executable. You would do that by
chmod a+x fakeswap.sh
after which you can run it simply by typing
./fakeswap.sh
(assuming it is in the current working directory; if not, use the full path.)
By the way, TEMP is a number of spaces equal to the length of NEW and OLD is the result of changing the last space in TEMP to 0. So OLD and NEW have the same length, meaning that the sed substitution won't change the size of the file.

Bash script prints "Command Not Found" on empty lines

Every time I run a script using bash scriptname.sh from the command line in Debian, I get Command Not found and then the result of the script.
The script works but there is always a Command Not Found statement printed on screen for each empty line. Each blank line is resulting in a command not found.
I am running the script from the /var folder.
Here is the script:
#!/bin/bash
echo Hello World
I run it by typing the following:
bash testscript.sh
Why would this occur?
Make sure your first line is:
#!/bin/bash
Enter your path to bash if it is not /bin/bash
Try running:
dos2unix script.sh
That wil convert line endings, etc from Windows to unix format. i.e. it strips \r (CR) from line endings to change them from \r\n (CR+LF) to \n (LF).
More details about the dos2unix command (man page)
Another way to tell if your file is in dos/Win format:
cat scriptname.sh | sed 's/\r/<CR>/'
The output will look something like this:
#!/bin/sh<CR>
<CR>
echo Hello World<CR>
<CR>
This will output the entire file text with <CR> displayed for each \r character in the file.
You can use bash -x scriptname.sh to trace it.
I also ran into a similar issue. The issue seems to be permissions. If you do an ls -l, you may be able to identify that your file may NOT have the execute bit turned on. This will NOT allow the script to execute. :)
As #artooro added in comment:
To fix that issue run chmod +x testscript.sh
This might be trivial and not related to the OP's question, but I often made this mistaken at the beginning when I was learning scripting
VAR_NAME = $(hostname)
echo "the hostname is ${VAR_NAME}"
This will produce 'command not found' response. The correct way is to eliminate the spaces
VAR_NAME=$(hostname)
On Bash for Windows I've tried incorrectly to run
run_me.sh
without ./ at the beginning and got the same error.
For people with Windows background the correct form looks redundant:
./run_me.sh
If the script does its job (relatively) well, then it's running okay. Your problem is probably a single line in the file referencing a program that's either not on the path, not installed, misspelled, or something similar.
One way is to place a set -x at the top of your script or run it with bash -x instead of just bash - this will output the lines before executing them and you usually just need to look at the command output immediately before the error to see what's causing the problem
If, as you say, it's the blank lines causing the problems, you might want to check what's actaully in them. Run:
od -xcb testscript.sh
and make sure there's no "invisible" funny characters like the CTRL-M (carriage return) you may get by using a Windows-type editor.
use dos2unix on your script file.
for executing that you must provide full path of that
for example
/home/Manuel/mywrittenscript
Try chmod u+x testscript.sh
I know it from here:
http://www.linuxquestions.org/questions/red-hat-31/running-shell-script-command-not-found-202062/
If you have Notepad++ and you get this .sh Error Message: "command not found"
or this autoconf Error Message "line 615:
../../autoconf/bin/autom4te: No such file or directory".
On your Notepad++, Go to Edit -> EOL Conversion then check Macinthos(CR).
This will edit your files. I also encourage to check all files with this command,
because soon such an error will occur.
Had the same problem. Unfortunately
dos2unix winfile.sh
bash: dos2unix: command not found
so I did this to convert.
awk '{ sub("\r$", ""); print }' winfile.sh > unixfile.sh
and then
bash unixfile.sh
Problems with running scripts may also be connected to bad formatting of multi-line commands, for example if you have a whitespace character after line-breaking "\". E.g. this:
./run_me.sh \
--with-some parameter
(please note the extra space after "\") will cause problems, but when you remove that space, it will run perfectly fine.
I was also having some of the Cannot execute command. Everything looked correct, but in fact I was having a non-breakable space right before my command which was ofcourse impossible to spot with the naked eye:
if [[ "true" ]]; then
highlight --syntax js "var i = 0;"
fi
Which, in Vim, looked like:
if [[ "true" ]]; then
highlight --syntax js "var i = 0;"
fi
Only after running the Bash script checker shellcheck did I find the problem.
I ran into this today, absentmindedly copying the dollar command prompt $ (ahead of a command string) into the script.
Make sure you haven´t override the 'PATH' variable by mistake like this:
#!/bin/bash
PATH="/home/user/Pictures/"; # do NOT do this
This was my mistake.
Add the current directory ( . ) to PATH to be able to execute a script, just by typing in its name, that resides in the current directory:
PATH=.:$PATH
You may want to update you .bashrc and .bash_profile files with aliases to recognize the command you are entering.
.bashrc and .bash_profile files are hidden files probably located on your C: drive where you save your program files.

Edit shell script while it's running

Can you edit a shell script while it's running and have the changes affect the running script?
I'm curious about the specific case of a csh script I have that batch runs a bunch of different build flavors and runs all night. If something occurs to me mid operation, I'd like to go in and add additional commands, or comment out un-executed ones.
If not possible, is there any shell or batch-mechanism that would allow me to do this?
Of course I've tried it, but it will be hours before I see if it worked or not, and I'm curious about what's happening or not happening behind the scenes.
It does affect, at least bash in my environment, but in very unpleasant way. See these codes. First a.sh:
#!/bin/sh
echo "First echo"
read y
echo "$y"
echo "That's all."
b.sh:
#!/bin/sh
echo "First echo"
read y
echo "Inserted"
echo "$y"
# echo "That's all."
Do
$ cp a.sh run.sh
$ ./run.sh
$ # open another terminal
$ cp b.sh run.sh # while 'read' is in effect
$ # Then type "hello."
In my case, the output is always:
hello
hello
That's all.
That's all.
(Of course it's far better to automate it, but the above example is readable.)
[edit] This is unpredictable, thus dangerous. The best workaround is , as described here put all in a brace, and before the closing brace, put "exit". Read the linked answer well to avoid pitfalls.
[added] The exact behavior depends on one extra newline, and perhaps also on your Unix flavor, filesystem, etc. If you simply want to see some influences, simply add "echo foo/bar" to b.sh before and/or after the "read" line.
Try this... create a file called bash-is-odd.sh:
#!/bin/bash
echo "echo yes i do odd things" >> bash-is-odd.sh
That demonstrates that bash is, indeed, interpreting the script "as you go". Indeed, editing a long-running script has unpredictable results, inserting random characters etc. Why? Because bash reads from the last byte position, so editing shifts the location of the current character being read.
Bash is, in a word, very, very unsafe because of this "feature". svn and rsync when used with bash scripts are particularly troubling, because by default they "merge" the results... editing in place. rsync has a mode that fixes this. svn and git do not.
I present a solution. Create a file called /bin/bashx:
#!/bin/bash
source "$1"
Now use #!/bin/bashx on your scripts and always run them with bashx instead of bash. This fixes the issue - you can safely rsync your scripts.
Alternative (in-line) solution proposed/tested by #AF7:
{
# your script
exit $?
}
Curly braces protect against edits, and exit protects against appends. Of course, we'd all be much better off if bash came with an option, like -w (whole file), or something that did this.
Break your script into functions, and each time a function is called you source it from a separate file. Then you could edit the files at any time and your running script will pick up the changes next time it gets sourced.
foo() {
source foo.sh
}
foo
Good question!
Hope this simple script helps
#!/bin/sh
echo "Waiting..."
echo "echo \"Success! Edits to a .sh while it executes do affect the executing script! I added this line to myself during execution\" " >> ${0}
sleep 5
echo "When I was run, this was the last line"
It does seem under linux that changes made to an executing .sh are enacted by the executing script, if you can type fast enough!
An interesting side note - if you are running a Python script it does not change. (This is probably blatantly obvious to anyone who understands how shell runs Python scripts, but thought it might be a useful reminder for someone looking for this functionality.)
I created:
#!/usr/bin/env python3
import time
print('Starts')
time.sleep(10)
print('Finishes unchanged')
Then in another shell, while this is sleeping, edit the last line. When this completes it displays the unaltered line, presumably because it is running a .pyc? Same happens on Ubuntu and macOS.
I don't have csh installed, but
#!/bin/sh
echo Waiting...
sleep 60
echo Change didn't happen
Run that, quickly edit the last line to read
echo Change happened
Output is
Waiting...
/home/dave/tmp/change.sh: 4: Syntax error: Unterminated quoted string
Hrmph.
I guess edits to the shell scripts don't take effect until they're rerun.
If this is all in a single script, then no it will not work. However, if you set it up as a driver script calling sub-scripts, then you might be able to change a sub-script before it's called, or before it's called again if you're looping, and in that case I believe those changes would be reflected in the execution.
I'm hearing no... but what about with some indirection:
BatchRunner.sh
Command1.sh
Command2.sh
Command1.sh
runSomething
Command2.sh
runSomethingElse
Then you should be able to edit the contents of each command file before BatchRunner gets to it right?
OR
A cleaner version would have BatchRunner look to a single file where it would consecutively run one line at a time. Then you should be able to edit this second file while the first is running right?
Use Zsh instead for your scripting.
AFAICT, Zsh does not exhibit this frustrating behavior.
usually, it uncommon to edit your script while its running. All you have to do is to put in control check for your operations. Use if/else statements to check for conditions. If something fail, then do this, else do that. That's the way to go.
Scripts don't work that way; the executing copy is independent from the source file that you are editing. Next time the script is run, it will be based on the most recently saved version of the source file.
It might be wise to break out this script into multiple files, and run them individually. This will reduce the execution time to failure. (ie, split the batch into one build flavor scripts, running each one individually to see which one is causing the trouble).

rm fails to delete files by wildcard from a script, but works from a shell prompt

I've run into a really silly problem with a Linux shell script. I want to delete all files with the extension ".bz2" in a directory. In the script I call
rm "$archivedir/*.bz2"
where $archivedir is a directory path. Should be pretty simple, shouldn't it? Somehow, it manages to fail with this error:
rm: cannot remove `/var/archives/monthly/April/*.bz2': No such file or directory
But there is a file in that directory called test.bz2 and if I change my script to
echo rm "$archivedir/*.bz2"
and copy/paste the output of that line into a terminal window the file is removed successfully. What am I doing wrong?
TL;DR
Quote only the variable, not the whole expected path with the wildcard
rm "$archivedir"/*.bz2
Explanation
In Unix, programs generally do not interpret wildcards themselves. The shell interprets unquoted wildcards, and replaces each wildcard argument with a list of matching file names.
if $archivedir might contain spaces, then rm $archivedir/*.bz2 might not do what you
You can disable this process by quoting the wildcard character, using double or single quotes, or a backslash before it. However, that's not what you want here - you do want the wildcard expanded to the list of files that it matches.
Be careful about writing rm $archivedir/*.bz2 (without quotes). The word splitting (i.e., breaking the command line up into arguments) happens after $archivedir is substituted. So if $archivedir contains spaces, then you'll get extra arguments that you weren't intending. Say archivedir is /var/archives/monthly/April to June. Then you'll get the equivalent of writing rm /var/archives/monthly/April to June/*.bz2, which tries to delete the files "/var/archives/monthly/April", "to", and all files matching "June/*.bz2", which isn't what you want.
The correct solution is to write:
rm "$archivedir"/*.bz2
Your original line
rm "$archivedir/*.bz2"
Can be re-written as
rm "$archivedir"/*.bz2
to achieve the same effect. The wildcard expansion is not taking place properly in your existing setup. By shifting the double-quote to the "front" of the file path (which is legitimate) you avoid this.
Just to expand on this a bit, bash has fairly complicated rules for dealing with metacharacters in quotes. In general
almost nothing is interpreted in single-quotes:
echo '$foo/*.c' => $foo/*.c
echo '\\*' => \\*
shell substitution is done inside double quotes, but file metacharacters aren't expanded:
FOO=hello; echo "$foo/*.c" => hello/*.c
everything inside backquotes is passed to the subshell which interprets them. A shell variable that is not exported doesn't get defined in the subshell. So, the first command echoes blank, but the second and third echo "bye":
BAR=bye echo `echo $BAR`
BAR=bye; echo `echo $BAR`
export BAR=bye; echo `echo $BAR`
(And getting this to print the way you want it in SO takes several tries is apparently impossible...)
The quotes are causing the string to be interpreted as a string literal, try removing them.
I've seen similar errors when calling a shell script like
./shell_script.sh
from another shell script. This can be fixed by invoking it as
sh shell_script.sh
Why not just rm -rf */*.bz2? Works for me on OSX.

Resources