Bad substitution in bash script - linux

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.

Related

Command works in terminal but not as alias in profile.d

I have a problem regarding an alias file in /etc/profile.d/. This isn't anything important. I'm just interested why it isn't working as expected.
So basically I have the file 00-alias.sh at the path mentioned above and I wanted to make a shortcut which reads a specific line of a file. So this is my code:
alias lnn='sed -n "${1}p" < "${2}"'
With that code I should be able to perform a command like
$ lnn 4 test.txt
However, this doesn't work. I simply get the error
-bash: : No such file or directory
Now I thought, ok, maybe relative paths aren't working because the file is located at the path /etc/profile.d/00-alias.sh
So I went ahead and made a new alias like
alias pwd2='echo $(pwd)'
Then updated the profile.d with
source /etc/profile.d/00-alias.sh
And just tried pwd2 but that echoed the path I was currently in. So in theory the file can be found with the command I wrote. I still tried to pass the file to my alias with absolute path like
$ lnn 4 /var/test.txt
Still same error as above.
But, if I enter the command of the alias in the terminal like
sed -n "4p" < test.txt
It works perfectly fine. No matter if I put quotes around test.txt
And here is another weird thing: If I write
alias lnn='sed -n "${1}p" < ${2}'
without the quotes around ${2} I get the error
-bash: ${2}: ambiguous redirect
In the terminal it works just fine...
So, what am I doing wrong? Does anyone have an idea on this? I'd love to know my mistake. But as I said, this isn't a real problem as I'm just curious why bash behaves like that.
Aliases in bash do not take parameters of any form. Save the pain and use a function instead.
function lnn() {
sed -n "${1}p" < "${2}"
}
Add the function to the file 00-alias.sh and source it once before calling the function from command-line.
source /etc/profile.d/00-alias.sh
lnn 4 test.txt
See more information at BashFAQ/80: How can I make an alias that takes an argument?
You can't. Aliases in bash are extremely rudimentary, and not really suitable to any serious purpose. The bash man page even says so explicitly:
An excerpt from the GNU bash man page, about aliases
.. There is no mechanism for using arguments in the replacement text. If arguments are needed, a shell function should be used.
On a side note the problem has nothing to do with relative paths (or) so, just remember aliases are not allowed in scripts. They're only allowed in interactive shells. If you're writing a script, always use a function instead.

Appending to file with sudo access

I am trying to append line to an existing file owned by root and I have to do this task with about 100 servers. So I created servers.txt with all the IPs and the ntp.txt file which will have the lines that I need to append. I am executing the following script and I am not achieving what I am trying to. Can someone please suggest what needs to be corrected?
!/bin/bash
servers=`cat servers.txt`;
for i in $servers;
do
cat ntp.txt | ssh root#${i} sudo sh -c "cat >>ntp.conf""
done
Here are some issues; not sure I found all of them.
The shebang line lacks the # which is significant and crucial.
There is no need to read the server names into a variable, and in addition to wasting memory, you are exposing yourself to a number of potential problems; see https://mywiki.wooledge.org/DontReadLinesWithFor
Unless you specifically require the shell to do whitespace tokenization and wildcard expansion on a value, put it in double quotes (or even single quotes, but this inhibits variable expansion, which you still want).
If you are logging in as root, no need to explicitly sudo.
ssh runs a shell for you; no need to explicitly sh -c your commands.
On the other hand, you want to avoid running a root shell if you can. A common way to get to append to a file without having to spawn a shell just to be able to redirect is to use tee -a (just drop the -a to overwrite instead of append). Also printing the file to standard output is an undesired effect (some would say the main effect rather than side effect of tee but let's just not go there) so you often redirect to /dev/null to avoid having the text also spill onto your screen.
You probably want to avoid a useless use of cat if only just to avoid having somebody point out to you that it's useless.
#!/bin/bash
while read -r server; do
do
ssh you#"$server" sudo tee -a /etc/ntp.conf <ntp.txt >/dev/null
done <servers.txt
I changed the code to log in as you but it's of course something you will need to adapt to suit your environment. (If you log in as yourself, you usually just ssh server without explicitly specifying a user name.)
As per your comment, I also added a full path to the destination file /etc/ntp.conf
A more disciplined approach to server configuration is to use something like CFengine2 to manage configurations.

$0 gives different results on Redhat versus Ubuntu?

I have the following script created by some self-claimed bash expert:
SCRIPT_LOCATION="$(readlink -f $0)"
SCRIPT_DIRECTORY="$(dirname ${SCRIPT_LOCATION})"
export PYTHONPATH="${PYTHONPATH}:${SCRIPT_DIRECTORY}/util"
That runs nicely on my local Ubuntu 16.04. Now I wanted to use it on our RH 7.2 servers; and there I got an error message from readlink; about being called with bad parameters.
Then I figured: on Ubuntu, $0 gives "bash"; whereas on RH, it gives "-bash".
EDIT: script is invoked as . ourscript.sh
Questions:
Any idea why that is?
When I change my script to use a hardcoded readlink -f bash the whole things works. Are there "better" ways for fixing this?
Feel free to also explain what readlink -f bash is actually doing ;-)
As the script is sourced the readlink -f $0 is pointless as it will just show you the command used to run the shell you are currently using.
To explain the difference in command lets look at the bash man page:
A login shell is one whose first character of argument zero is a -, or one started with the --login option.
When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. The --noprofile option may be used when the shell is started to inhibit this behavior.
So guessing ubuntu starts with the noprofile option.
As for readlink, we can again look at the man page
-f, --canonicalize
canonicalize by following every symlink in every component of the given name recursively; all but the last component must exist
Therefore it follows symlinks to the base.
Using readlink -f with any non qualified path will result in it just appending the last arg to your current working directory which will not actually show where the script is run.
Try putting any random string instead of bash after it and will see the script is unaffected.
e.g
readlink -f dafsfdsf
Returns
/home/me/testscript/dafsfdsf

Risk of unquoted bash parameters/variables

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?

Shell Script - Linux

I want to write a very simple script , which takes a process name , and return the tail of the last file name which contains the process name.
I wrote something like that :
#!/bin/sh
tail $(ls -t *"$1"*| head -1) -f
My question:
Do I need the first line?
Why isn't ls -t *"$1"*| head -1 | tail -f working?
Is there a better way to do it?
1: The first line is a so called she-bang, read the description here:
In computing, a shebang (also called a
hashbang, hashpling, pound bang, or
crunchbang) refers to the characters
"#!" when they are the first two
characters in an interpreter directive
as the first line of a text file. In a
Unix-like operating system, the
program loader takes the presence of
these two characters as an indication
that the file is a script, and tries
to execute that script using the
interpreter specified by the rest of
the first line in the file
2: tail can't take the filename from the stdin: It can either take the text on the stdin or a file as parameter. See the man page for this.
3: No better solution comes to my mind: Pay attention to filenames containing spaces: This does not work with your current solution, you need to add quotes around the $() block.
$1 contains the first argument, the process name is actually in $0. This however can contain the path, so you should use:
#!/bin/sh
tail $(ls -rt *"`basename $0`"*| head -1) -f
You also have to use ls -rt to get the oldest file first.
You can omit the shebang if you run the script from a shell, in that case the contents will be executed by your current shell instance. In many cases this will cause no problems, but it is still a bad practice.
Following on from #theomega's answer and #Idan's question in the comments, the she-bang is needed, among other things, because some UNIX / Linux systems have more than one command shell.
Each command shell has a different syntax, so the she-bang provides a way to specify which shell should be used to execute the script, even if you don't specify it in your run command by typing (for example)
./myscript.sh
instead of
/bin/sh ./myscript.sh
Note that the she-bang can also be used in scripts written in non-shell languages such as Perl; in the case you'd put
#!/usr/bin/perl
at the top of your script.

Resources