Explain this shell script - linux

Autoconf documentation recommends this snippet for portability:
# Create a temporary directory $tmp in $TMPDIR (default /tmp).
# Use mktemp if possible; otherwise fall back on mkdir,
# with $RANDOM to make collisions less likely.
: ${TMPDIR=/tmp}
{
tmp=`
(umask 077 && mktemp -d "$TMPDIR/fooXXXXXX") 2>/dev/null
` &&
test -n "$tmp" && test -d "$tmp"
} || {
tmp=$TMPDIR/foo$$-$RANDOM
(umask 077 && mkdir "$tmp")
} || exit $?
I don’t understand the first line:
Why does it begin with a :?
Why is the TMPDIR variable used just after that?

: ${TMPDIR=/tmp} means:
: is null command in bash , but shell assign /tmp to TMPDIR variable if content of TMPDIR is null

The ": ${TMPDIR=/tmp}" line is invoking ":" shell built-in, which does nothing, with a single argument - result of "${TMPDIR=/tmp}" parameter expansion.
This parameter expansion assigns "/tmp" to TMPDIR variable, if it was unset (not set to anything before, even empty string), and then expands to the value of TMPDIR, which is ignored by ":" builtin.

Related

tcsh alias with if-then-else condition

I am trying to have an alias with if-then-else condition
on a command result.
the alias will get a file pattern
if there is just one file it will open the file
if there is more or less then 1 files - it will indicate with a message.
the alias I have tried is:
alias atest 'if \("ls \!:1" > 0 \) "nedit ls \!:1" else "echo cant open the file" '
There are a couple of ways, most of them are not very elegant, but the following is the best looking and easiest to create that I have discovered:
alias aliasname '`if (condition == check) echo "echo"` >&/dev/null && code_if_true || code_if_false'
You have the ability to nest if statements following this format, and can also use it as a checker for arguments if you change the interior of the if statement to be
if ("\!:1" =~ "check")
The >&/dev/null is to clean up the output of the function, but is not necessary. If you are interested, there is also a way to make for statements within aliases, but that method is a lot less elegant. I haven't used it but it is necessary if you wish to create an alias with a varying number of arguments. Again, that one is ugly, so at that point I'd just write a function.
You can't do this with an alias. An alias can only expand into a one-line command, but if requires multiple lines.
Instead, use a shell script. The script doesn't have to be in the same language you use interactively, so you can use POSIX shell, which is generally considered much better for programming than C shell (see Csh Programming Considered Harmful).
#!/bin/sh
if [ $# -eq 1 ]
then nedit "$1"
else
echo "Can't open the file"
exit 1
fi
Put this in a file named atest, give it execute permissions, and put it in a directory that's in your $PATH.
Building on Jonathan Roberts solution. This is part of a bash script on the local server. Checks for user XXXXX if true, sends through ssh the BASH command. Otherwise sends the TCSH command. The command checks to see if a directory exists returns true or false
if [[ ${LOCAL_USER} == "XXXXX" ]]; then
LOCAL_DIR_EXIST_CHECK=$(ssh -v -q -i ~/.ssh/"${LOCAL_SSH_KEY_FILE}" "${LOCAL_USER}#${LOCAL_SERVER}" "if [[ -d ${LOCAL_CLIENT_DIRECTORY} ]];then echo 'TRUE'; else echo 'FALSE'; fi")
else
LOCAL_DIR_EXIST_CHECK=$(ssh -v -q -i ~/.ssh/"${LOCAL_SSH_KEY_FILE}" "${LOCAL_USER}#${LOCAL_SERVER}" '`if ( -d ' ${LOCAL_CLIENT_DIRECTORY} ') echo "echo"` > & /dev/null && echo TRUE || echo FALSE')
fi
Actually, tcsh can have aliases with multiple lines in them. Just finish each line with backslash () and continue writing the alias on a next line.
~> alias atest 'set __atest_pattern="\!:1" \
? set __atest_files=( "`ls -d $__atest_pattern`" ) \
? if ( $#__atest_files == 1 ) then \
? nedit "$__atest_files[1]" \
? else if ( $#__atest_files > 1 ) then \
? echo "Too many files ($#__atest_files) are matched to '\''$__atest_pattern'\''" \
? endif'
~> alias atest
set __atest_pattern="!:1"
set __atest_files=( "`ls -d $__atest_pattern`" )
if ( $#__atest_files == 1 ) then
nedit "$__atest_files[1]"
else if ( $#__atest_files > 1 ) then
echo "Too many files ($#__atest_files) are matched to '$__atest_pattern'"
endif
~> atest *
Too many files (73) are matched to '*'
~> atest dummy
ls: cannot access dummy: No such file or directory
~> atest /dummy/*
ls: No match.
~> atest .cshrc
# Actually nedit was invoked here
~>
Using #JonathanRoberts answer I was able to finally improve an alias for exit that I use in shells inside screen so that I don't accidentally exit when I really want to detach. So, now I can override the faked exit with exit now if I want and really exit:
In ~/.tcshrc
if ($?TERM && $TERM =~ screen.*) then
#OLD ALIAS# alias exit "echo 'exit disabled (via alias)'; screen -d"
alias exit '`if ("\!:$" == "now") echo "echo"` >&/dev/null && exec false || echo "really? use: exit now" ; screen -d'
endif

How is this bash script launching a program?

I am looking into how a particular exploit works, and I chose to look at one in the program 'chkrootkit' which allows for any user to run a malicious file as root. The source code for this vulnerable shellscript is as follows
slapper (){
SLAPPER_FILES="${ROOTDIR}tmp/.bugtraq ${ROOTDIR}tmp/.bugtraq.c"
SLAPPER_FILES="$SLAPPER_FILES ${ROOTDIR}tmp/.unlock ${ROOTDIR}tmp/httpd \
${ROOTDIR}tmp/update ${ROOTDIR}tmp/.cinik ${ROOTDIR}tmp/.b"a
SLAPPER_PORT="0.0:2002 |0.0:4156 |0.0:1978 |0.0:1812 |0.0:2015 "
OPT=-an
STATUS=0
file_port=
if ${netstat} "${OPT}"|${egrep} "^tcp"|${egrep} "${SLAPPER_PORT}">
/dev/null 2>&1
then
STATUS=1
[ "$SYSTEM" = "Linux" ] && file_port=`netstat -p ${OPT} | \
$egrep ^tcp|$egrep "${SLAPPER_PORT}" | ${awk} '{ print $7 }' |
tr -d :`
fi
for i in ${SLAPPER_FILES}; do
if [ -f ${i} ]; then
file_port=$file_port $i
STATUS=1
fi
done
if [ ${STATUS} -eq 1 ] ;then
echo "Warning: Possible Slapper Worm installed ($file_port)"
else
if [ "${QUIET}" != "t" ]; then echo "not infected"; fi
return ${NOT_INFECTED}
fi
}
I know that the reason the exploit works is because the line 'file_port=$file_port $i' will execute all files specified in $SLAPPER_FILES as the user chkrootkit is running (usually root), if $file_port is empty, because of missing quotation marks around the
variable assignment."
My question is why does the command
file_port=$file_port $i
result in execution of the file? Assuming that $i refers to the path of the file (/tmp/update)
I can see that file_port might be changed to some long netstat command in the previous if statement, is this something to do with it?
I've been trying to get my head around this all day to no avail, so at this point any help will be greatly appreciated :)
This is the one-shot variable assignment feature of any Bourne shell. Any command can be prefixed with zero or more variable assignments:
VAR1=VALUE1 VAR2=VALUE2 command arguments ...
Runs command arguments ... with the respective environment variables set for just this command. A typical use might be
EDITOR=vim crontab -e

Linux difference between when to use parentheses

Why do I get extra empty line when running 2). To me 1 is like 2. So why the extra line in 2)?
1)
export p1=$(cd $(dirname $0) && pwd)
# ^
echo p1
2)
export p2=$(cd $(dirname $0)) && pwd
# ^
echo p2
$echo $0
/bin/bash
$ echo $(cd $(dirname $0) && pwd)
/bin
$ echo $(cd $(dirname $0)) && pwd
/home/user
$
In the 1st expression it becomes echo $(cd /bin && pwd). Therefore the inner 2 commands execute in a subshell and return back the pwd value which is then echoed.
In the 2nd expression it gets reduced to echo $(cd /bin) && pwd. Therefore only the cd command executes in a subshell and returns nothing to echo (hence by default echo just prints an empty line). Since echo ran successfully(exit code=0) && results in true and pwd cmd is run in current shell and pwd gets printed
p1 captures the output of cd (empty) and pwd.
p2 only captures the output of cd, and then runs pwd without redirection.
echo p1 prints a literal p1 (with a newline). I guess you didn't actually copy-paste from your terminal, but instead typed in some thing else.
peter#tesla:~$ export p2=$(true) && pwd
/home/peter
peter#tesla:~$ echo "x${p2}x"
xx
cd in a subshell doesn't affect the parent shell's pwd, so I just substituted the true command to make it more readable.

Get current directory (without full path) in Fish Shell

A buddy of mine finally got me to start using Fish Shell and I'm trying to set it up similar to how I had Bash. The PS1 in my .bash_profile listed the current directory I was in, followed by a >. It, however, wasn't the absolute path (e.g. /Users/me/Documents/... or ~/Documents/...). If I was in /Users/me/Documents/projects/Go/project1/, the prompt would simply say project1 >.
Is there a Fish Shell alternative to the \W substitution available for Bash? Again, I just want the folder I'm in, not the full path. I know you can use the echo (pwd) for all that.
I have looked into the basename program, and echo "${PWD##*/}", but these appear to only work in Bash.
Taken from #Jubobs' answer:
basename is just a Unix utility; it's not associated to a particular shell, and should work equally well in Bash and Fish.
It appeared I was using basename in the wrong context, and without a suffix.
This was solved by using the following:
function fish_prompt
echo (basename $PWD) "><> "
end
An alternative: fish ships with a function called prompt_pwd which displays /Users/me/Documents/projects/Go/project1/ as ~/D/p/G/project1
function fish_prompt
echo (prompt_pwd) "><> "
end
The complete code of prompt_pwd.fish is below. You must put it inside the directory ~/.config/fish/functions/
function prompt_pwd --description "Print the current working directory, shortened to fit the prompt"
set -q argv[1]
and switch $argv[1]
case -h --help
__fish_print_help prompt_pwd
return 0
end
# This allows overriding fish_prompt_pwd_dir_length from the outside (global or universal) without leaking it
set -q fish_prompt_pwd_dir_length
or set -l fish_prompt_pwd_dir_length 1
# Replace $HOME with "~"
set realhome ~
# #EDITED by Thiago Andrade
set tmpdir (basename $PWD)
set -l tmp (string replace -r '^'"$realhome"'($|/)' '~$1' $tmpdir)
# ORIGINAL VERSION
# set -l tmp (string replace -r '^'"$realhome"'($|/)' '~$1' $PWD)
if [ $fish_prompt_pwd_dir_length -eq 0 ]
echo $tmp
else
# Shorten to at most $fish_prompt_pwd_dir_length characters per directory
string replace -ar '(\.?[^/]{'"$fish_prompt_pwd_dir_length"'})[^/]*/' '$1/' $tmp
end
end
Then you'll see something like this
This is my function in
~/.config/fish/functions/prompt_pwd.fish
and this seems to work fine
function prompt_pwd
set -q argv[1]
and switch $argv[1]
case -h --help
__fish_print_help prompt_pwd
return 0
end
set -q fish_prompt_pwd_dir_length
or set -l fish_prompt_pwd_dir_length 1
set ttmp $PWD
set ttmp (string replace -r '^'$HOME'($|/)' '~$1' $PWD)
set -l tmp (basename $ttmp)
if [ $fish_prompt_pwd_dir_length -eq 0 ]
echo $tmp
else
string replace -ar '(\.?[^/]{'"$fish_prompt_pwd_dir_length"'})[^/]*/' '$1/' $tmp
end
end

Is there a reasonable way to attach new path to PATH in bashrc?

I constantly need to attach new paths to the PATH environment variable in .bashrc, like below:
export PATH=/usr/local/bin:$PATH
Then to make it take effect, I always do 'source ~/.bashrc' or '. ~/.bashrc', while I found one shortcoming of doing so which make me uncomfortable.
If I keep doing so, the PATH will getting longer and longer with many duplicated entries, for example in the previous command, if I source it twice, the value of PATH will be
PATH=/usr/local/bin:/usr/local/bin:/usr/local/bin:$PATH(<-the original path).
Is there a more decent way to attach new path to PATH in bashrc without making it ugly?
Another way is to check if OPATH isn't set. If it is, set it to PATH. This will be your original PATH.
if [ "$OPATH" == "" ];
then
OPATH=$PATH
fi
PATH=~/bin:$OPATH
(Code is untested...)
If you're willing to entertain a change of shell, zsh has declare -U for this exact purpose: it'll automatically strip duplicates from an array while maintaining precedence. It also lets you use $path instead of $PATH.
% PATH=a:b:c:d:c:b:a
% echo $PATH
a:b:c:d:c:b:a
% declare -U PATH
% echo $PATH
or, for improved readability, you can use the array form, which is kept synchronized automatically:
% path=(a b c d c b a)
% print $path
a b c d c b a
% print $PATH
a:b:c:d:c:b:a
% declare -U path
% print $path
a b c d
% print $PATH
a:b:c:d
My approach is like rcollyer's, but more universal, deals with precedence and uses much more code.
function append () {
local val
eval val=\$$1
if [[ x$val = x ]] ; then
eval $1=$2
else
eval $1="$val:$2"
fi
}
function is_in() {
local pattern
pattern=":$1\$|^$1:|:$1:"
echo $2 | egrep -q "$pattern" && return 0
return 1
}
function append_if_absent() {
local val
eval val=\$$1
if ! is_in "$2" "$val" ; then
append "$1" "$2"
fi
}
export ROOTSYS=/usr/local/root
append_if_absent LD_LIBRARY_PATH $ROOTSYS/lib/root
append_if_absent PATH $ROOTSYS/bin
My solution is the one liner:
export PATH=`echo :<new path>:${PATH} | sed -e 's/\:/\n/g' | sort | uniq \
| awk 'BEGIN {ORS=":"} {print $0}'`
where sed replaces : with a newline, sort and uniq strip out any duplicates, and awk rebuilds the path. This does have the disadvantage that the order is not maintained, so if you want to have programs in one location to have precedence over ones in other locations, this will cause problems. I haven't implemented it, but I imagine you could use perl to maintain these in order while stripping out the duplicates.
Here is what I have been using for a long time: I added a function called addpath() to .bash_profile, or .bashrc and then I can add a directory to the path knowing there will be no duplicate. For example:
addpath ~/bin
addpath ~/myproj/bin
Here is the source for addpath():
function addpath()
{
if [ $# -eq 0 ]
then
echo " Usage: addpath dir ..."
return 1
fi
local p
local dir
local IFS=:
local found
for dir; do
found=0
for p in $PATH; do
if [ "$p" = "$dir" ]; then
found=1
fi
done
if [ "_$found" = "_0" ]; then
PATH=$PATH:$dir
fi
done
}
From my .bashrc:
pathadd() {
if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
PATH="$PATH:$1"
fi
}
pathadd /usr/local/bin
pathadd ~/bin
...etc
Note that this adds directories to the end of the path; make the obvious change to add them to the beginning.

Resources