Why a hard coded string var is changing when the bash script is run as root? - bash

I am batch converting some pictures with a quick and dirty bash script using ufraw :
IFS=$'\n'
PICS="/media/disk/kevin/Images/";
for pic in $(find $PICS -name "*CR2");
do
ufraw-batch $pic --out-type jpg --size=2048 --overwrite --out-path=$PICS;
rm -f $pic
done;
IFS=" ";
It's running fine with the usual rights, but if I run it with :
sudo ./convert.sh
I got this strange error :
find: "/media/disk/kevi" : no such file or directory.
I made it :
IFS=$'\n'
PICS="/media/disk/kevin/Images/";
echo PICS;
Then I got surprisingly :
/media/disk/kevi /Images/
WTF ?

You're winding up with $IFS being "n"; it's not interpreting \n as a newline but as a meaninglessly escaped n. I vaguely suspect you're running into some sort of ill-documented protection of $IFS for root (since it's a well known exploit vector), but you might want to try embedding a literal newline instead of a symbolic one in your script using ctrl-v enter.

If you don't have an explict #! line in your script, chances are that the sudo-ed command is being run under /bin/sh rather than /bin/bash and, if you're running on a recent Linux system, chances are that /bin/sh is dash rather than bash. The maintainers of dash maintain that IFS is not supposed to interpret escape sequences (see, for instance, here).
$ more convert.sh
IFS=$'\n'
PICS="/media/disk/kevin/Images/";
echo $PICS;
ps
$ ./convert.sh
/media/disk/kevin/Images/
PID TTY TIME CMD
30827 pts/0 00:00:01 bash
32042 pts/0 00:00:00 bash
32043 pts/0 00:00:00 ps
$ sudo ./convert.sh
/media/disk/kevi /Images/
PID TTY TIME CMD
32044 pts/0 00:00:00 sh
32045 pts/0 00:00:00 ps
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 2009-08-06 19:10 /bin/sh -> dash
So the behavior you see is only indirectly related to running under root. You'd see the same if you explicitly used dash. And another way to work around the problem would be to include an explict #!/bin/bash in your script.

Your code seems to have a typo. If the actual code is IFS='\n' or IFS="\n", you've set the separator to a list of two characters, \ and n. If IFS=\n, then one char A POSIX shell doesn't interpret \n as LF. It has nothing to do with root.
$ BLAH='\n'
$ echo $BLAH
\n
$ BLAH="\n"
$ echo $BLAH
\n
$ BLAH=\n
$ echo $BLAH
n
$ BLAH="Both\n\and\aregone"
$ echo $BLAH
Both a d arego e

Related

Edit an executable with bash without recompiling it?

I'm trying to write a script for editing an executable (e.g. /bin/bash) in this way:
Search for a string -
Replace old string with new string -
Save changes to original file (obviously I'm trying on a copy of /bin/bash, for security reason)
My string is a single word. How can I do it?
You can easily modify a string in a binary file with sed but as
noted in the comments it will most often only work when old and new string have the same length. With bash for example:
$ cp /bin/bash .
$ strings bash | grep such
describe_pid: %ld: no such pid
it creates, on systems that allow such control.
such as cd which change the current directory.
%s: no such job
$ sed 's,no such job,no such boj,' -i bash
$ ./bash --noprofile --norc
bash-4.3$ kill %3333
bash: kill: %3333: no such boj
-i for sed is not in
POSIX
so if you want to achieve maximum portability:
$ cp /bin/bash .
$ sed 's,no such job,no such boj,' bash > bash.bak
$ mv bash.bak bash
$ chmod +x ./bash
$ ./bash --noprofile --norc
bash-4.3$ kill %3333
bash: kill: %3333: no such boj

Why no ouput for the code in non login-shell status?

Platform:debian8 + bash.
Running the following command in my terminal gives me the following result:
prompt> echo $0
/bin/bash
I want to get what shell login status ,echo give more info than echo $SHELL.
1.the shell type is bash
2.it is a non-login shell
prompt> echo $0
-/bin/bash
It means not only bash shell was used but only a login-shell.
When I create a file test.sh, containing only the line:
echo $0
running it produces:
prompt> /bin/bash test.sh
test.sh
In other words, I get the script name rather than the shell name. Is there a way to get the shell name /bin/bash instead?
echo $SHELL is not i want to get,echo $SHELL only contain shell type ,not telling me it is login shell or non-login shell.
To make my intent clarity,let's edite two files: ~/.profile and ~/.bashrc
vim .profile
varLog="i am login shell"
vim .bashrc
varLog="i am not login shell"
~$ /bin/bash
~$ echo $varLog
i am non-login shell
~$ /bin/bash --login
~$ echo $varLog
i am login shell
Now to edit a bash script.
vim /tmp/decideShell.sh
echo $varLog
Logout and login again.
debian8#hwy:~$ ls -al /tmp/decideShell.sh
-rw-r--r-- 1 debian8 debian8 13 Mar 8 09:40 /tmp/decideShell.sh
debian8#hwy:~$ /bin/bash /tmp/decideShell.sh
debian8#hwy:~$ /bin/bash --login /tmp/decideShell.sh
i am login shell
Why nothing output for /bin/bash /tmp/decideShell.sh?
Try the following:
myname=$(id -u -n)
myloginshell=$(grep "${myname}:" /etc/passwd | cut -d ':' -f 7)
printf "my login shell: %s\n" ${myloginshell}
Depends on your login privileges and security rules you will find your username in /etc/passwd or not (i.e. ldap authority or other). If your username is stored in this file, you can find your default login shell as the 7th column. The first column is your username.
If you are authorized by other way (ldap or special other) and didn't find your username in the passwd-file, you can change your profile (depends on your unix-version in .profile or .bashrc or whatelse). Set your own variable (i.e. myloginshell) with
myloginshell=$(echo $0 | sed -e 's/\-//')
and don't forget to export this variable. The complete path to your shell can everytime examinated with
type -p ${myloginshell}
Last but not least you can find some special informations with the shopt command (login_shell, restricted_shell, ...).
$0 has a special meaning and it's value depends on how the shell or the shell is interpreting the script is called. There are special cases that are explained in detail in the bash man page but in most cases it's the name of the shell or the shell script being interpreted ... so it's perfectly normal that your script returns it's name not the shell that is being used to interpret the script (and in your case the interpreter is implicit in the command used to execute it).
I don't really understand why you want to know some things that would be implicit of the way you executed the script or depend on the way the interpreter is specified in the script itself ... but you might have your reasons for doing that ... in which case you might want to look at this answer on stackexchange: https://unix.stackexchange.com/a/26782
As to which shell is interpreting $SHELL is the right place to look.
If you just want the shell without the path do a longest prefix removal on $SHELL like this:
echo ${SHELL##*/}
This script can give you some idea:
echo ppid pid command
me=$$
while [ $me != 1 ]; do
ps=$(ps h -o ppid,pid,args -p $me)
echo $ps
me=$(echo $ps |cut -d" " -f1)
done
Output in my case (Debian stable linux, launched from an mrxvt launched from putty) is:
ppid pid command
4921 4922 bash
3938 4921 bash
3937 3938 mrxvt -fn --fixed-medium-r---12------iso8859-15 -vb -sl 300
1 3937 -bash
This script prints out the tree of the process, starting from the current up to init (which is not printed because we know everything about it). For every line there is the parent pid, used to go up, the pid, and the full command line. You can vary the options to ps(1), and check its output. May be you are interested only in the first line printed (the current process), or in its parent ($PPID).
I hope it helps to start.
UPDATE after comment. Running this script via this command inside putty:
./sc.sh
the output is:
ppid pid command
3938 4004 -bash
3936 3938 -bash
2306 3936 sshd: root#pts/1
1 2306 /usr/sbin/sshd
and I can see that the shell I am in is a login shell (because of "-bash").
Instead, calling the script in the following way:
bash --login script.sh
the output turns to:
ppid pid command
3938 3945 bash --login sc.sh
3936 3938 -bash
2306 3936 sshd: auser#pts/1
1 2306 /usr/sbin/sshd
shows that the process per se is not in a login shell, but it has been launched from a login shell. I think that the methods used here can be used to detect any situation.

Which process executes script in terminal

when i write ./test1.sh in the MacOS terminal which process executes the script ?
I have installed on my machine oh-my-zsh and thus run zsh in my terminal.
Running test1.sh like so:
./test1.sh
outputs
1 март 2016/ 1 януари 2015/
1 март 2016/
./test3.sh: line 14: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...]
./test3.sh: line 15: януари: syntax error: invalid arithmetic operator (error token is "?нуари")
blah
while running it like this
zsh test3.sh
outputs
1 март 2016/
1 януари 2015/
blah
On the other hand running
declare -A newarray
newarray[януари]="1"
qn="януари"
echo ${newarray[$qn]}
outputs
1
Why is that ?
test3.sh
# backup IFS
SAVEIFS=$IFS
# set IFS to newline
IFS=$(echo -en "\n\b")
# get files
FILES=$(ls -1 -d */)
echo ${FILES}
IFS='\n'
read dirsNameArray <<< ${FILES}
echo ${dirsNameArray[0]}
declare -A monthMap
monthMap['януари']="1"
# monthMap[февруари]="2"
# monthMap[март]="3"
# monthMap[април]="4"
# monthMap[май]="5"
# monthMap[юни]="6"
# monthMap[юли]="7"
# monthMap[август]="8"
# monthMap[септември]="9"
# monthMap[октомври]="10"
# monthMap[ноември]="11"
# monthMap[декември]="12"
# iterate over files
IFS='\n'
for f in $FILES
do
echo "blah"
IFS=' '
# read -r dirNameArray <<< $f
# echo "${monthMap[${dirNameArray[2]}]}"
IFS='\n'
done
# restore $IFS
IFS=$SAVEIFS
You should always include a shebang in your scripts. If you want your shell script to be run by zsh then make sure that the topmost line of you script looks like that:
#!/bin/zsh
This will guarantee that your script will be executed by /bin/zsh (or whatever other executable you specified in the shebang).
If you want to find out what shell is used to execute your script, add the following line to it:
ps ho cmd $$
and see what it prints. If you want to know what shell is used in an interactive session, check if either $BASH_VERSION or $ZSH_VERSION is defined.
Let's find out what shell zsh uses to execute text files:
% echo 'ps -f $$' > script.sh && chmod +x script.sh && ./script.sh
UID PID PPID C STIME TTY STAT TIME CMD
slim 17311 16570 0 15:45 pts/0 S+ 0:00 sh ./script.sh
So it uses sh. That make sense, sh is the lowest common denominator, default shell.
To force a different shell, use #! in the first line of the text file:
% echo '#!/bin/zsh' > script.sh && echo 'ps -f $$' >> script.sh && chmod +x script.sh && ./script.sh
UID PID PPID C STIME TTY STAT TIME CMD
slim 17342 16570 0 15:46 pts/0 S+ 0:00 /bin/zsh ./script.sh
#! is a general purpose mechanism, so you can use it to execute pretty much anything that reads from stdin and ignores "comments" beginning with # -- perl, python, most shells, awk, even things like gnuplot.

Blank first line of shell script: explain behavior of UID variable

I have two very simple scripts, differing only by the presence of a blank first line:
$ cat test.bash
#!/bin/bash
echo ${UID}
$ cat test_blank.bash
#!/bin/bash
echo ${UID}
Now I run then, with and without nice:
$ ./test.bash
1060
$ ./test_blank.bash
1060
$ nice ./test.bash
1060
$ nice ./test_blank.bash
Please explain why, in the final case, the UID variable is unset. The behavior is the same when replacing nice with sudo or nohup.
Observe:
$ bash test_blank.bash
1060
$ dash test_blank.bash
bash produces output but dash, which is the default sh on debian-like systems, does not. This is because bash sets UID but dash does not. (POSIX does not require a shell to set UID.) So, the question becomes which shell executes the script.
When bash sees ./test.sh, it (bash) runs the script. When another command, such as nice, receives the script as an argument and the script does not have a valid shebang as the first line, then the default shell, likely dash, is run.
If you want UID in dash, or any other shell that does not provide it, use the id command:
UID=$(id -u)
Finding out which shell is running a script
To see which shell is running a script, use:
$ cat test2.sh
#!/bin/bash
ps $$
echo UID=${UID}
Under bash:
$ ./test2.sh
PID TTY STAT TIME COMMAND
1652 pts/12 S+ 0:00 bash -rcfile .bashrc
UID=1060
If we invoke it using nice, by contrast, we can see that it is running under /bin/sh and the UID variable is not assigned:
$ nice test2.sh
PID TTY STAT TIME COMMAND
1659 pts/12 SN+ 0:00 /bin/sh test2.sh
UID=

Bash script: how to get the whole command line which ran the script

I would like to run a bash script and be able to see the command line used to launch it:
sh myscript.sh arg1 arg2 1> output 2> error
in order to know if the user used the "std redirection" '1>' and '2>', and therefore adapt the output of my script.
Is it possible with built-in variables ??
Thanks.
On Linux and some unix-like systems, /proc/self/fd/1 and /proc/self/fd/2 are symlinks to where your std redirections are pointing to. Using readlink, we can query if they were redirected or not by comparing them to the parent process' file descriptor.
We will however not use self but $$ because $(readlink /proc/"$$"/fd/1) spawns a new shell so self would no longer refer to the current bash script but to a subshell.
$ cat test.sh
#!/usr/bin/env bash
#errRedirected=false
#outRedirected=false
parentStderr=$(readlink /proc/"$PPID"/fd/2)
currentStderr=$(readlink /proc/"$$"/fd/2)
parentStdout=$(readlink /proc/"$PPID"/fd/1)
currentStdout=$(readlink /proc/"$$"/fd/1)
[[ "$parentStderr" == "$currentStderr" ]] || errRedirected=true
[[ "$parentStdout" == "$currentStdout" ]] || outRedirected=true
echo "$0 ${outRedirected:+>$currentStdout }${errRedirected:+2>$currentStderr }$#"
$ ./test.sh
./test.sh
$ ./test.sh 2>/dev/null
./test.sh 2>/dev/null
$ ./test.sh arg1 2>/dev/null # You will lose the argument order!
./test.sh 2>/dev/null arg1
$ ./test.sh arg1 2>/dev/null >file ; cat file
./test.sh >/home/camusensei/file 2>/dev/null arg1
$
Do not forget that the user can also redirect to a 3rd file descriptor which is open on something else...!
Not really possible. You can check whether stdout and stderr are pointing to a terminal: [ -t 1 -a -t 2 ]. But if they do, it doesn't necessarily mean they weren't redirected (think >/dev/tty5). And if they don't, you can't distinguish between stdout and stderr being closed and them being redirected. And even if you know for sure they are redirected, you can't tell from the script itself where they point after redirection.

Resources