Difference in executing shell script by 'sh' and 'source' (and '.') [duplicate] - linux

This question already has answers here:
Echo newline in Bash prints literal \n
(22 answers)
Difference between sh and Bash
(11 answers)
Closed 2 years ago.
I have a tiny question.
When I execute my shell file by 'sh', it works differently.
(it printed '-e')
Google says "Check your first line(#!/bin/bash) and 'env | grep sh'"
But there no do difference in my eyes...
Would you tell me why it is?
+++
I tried changing '#!/bin/bash' → '#!/bin/dash', but noting changed.
Thanks for reading..
[my first code(first.sh)] -bash
#!/bin/bash
echo -e "hi"
[run script and result]
# . first.sh
Hi
# source first.sh
Hi
# sh first.sh
-e hi
[my second code(second.sh)] -dash
#!/bin/dash
echo -e "hi"
[run script and result]
nothing change...
[OS]
ubuntu 18.04
[environment]
# env | grep sh
SHELL=/bin/bash
# ll /bin | grep sh
-rwxr-xr-x 1 root root 1113504 6월 6 2019 bash*
-rwxr-xr-x 1 root root 121432 1월 25 2018 dash*
lrwxrwxrwx 1 root root 4 6월 6 2019 rbash -> bash*
lrwxrwxrwx 1 root root 4 7월 18 2019 sh -> dash*
lrwxrwxrwx 1 root root 4 7월 18 2019 sh.distrib -> dash*

that is because the sh shell interpreter does not recognize -e as a flag and prints it as a regular string.
source and . use your SHELL env, which is bash, whom recognizes the -e flag and does not treat it as a string.

Related

why sh softlink to bash doesn't work? [duplicate]

I have a shell script which uses process substitution
The script is:
#!/bin/bash
while read line
do
echo "$line"
done < <( grep "^abcd$" file.txt )
When I run the script using sh file.sh I get the following output
$sh file.sh
file.sh: line 5: syntax error near unexpected token `<'
file.sh: line 5: `done < <( grep "^abcd$" file.txt )'
When I run the script using bash file.sh, the script works.
Interestingly, sh is a soft-link mapped to /bin/bash.
$ which bash
/bin/bash
$ which sh
/usr/bin/sh
$ ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 9 Jul 23 2012 /usr/bin/sh -> /bin/bash
$ ls -l /bin/bash
-rwxr-xr-x 1 root root 648016 Jul 12 2012 /bin/bash
I tested to make sure symbolic links are being followed in my shell using the following:
$ ./a.out
hello world
$ ln -s a.out a.link
$ ./a.link
hello world
$ ls -l a.out
-rwx--x--x 1 xxxx xxxx 16614 Dec 27 19:53 a.out
$ ls -l a.link
lrwxrwxrwx 1 xxxx xxxx 5 May 14 14:12 a.link -> a.out
I am unable to understand why sh file.sh does not execute as /bin/bash file.sh since sh is a symbolic link to /bin/bash.
Any insights will be much appreciated. Thanks.
When invoked as sh, bash enters posix
mode after the startup files are read. Process substitution is not recognized in posix mode. According to posix, <(foo) should direct input from the file named (foo). (Well, that is, according to my reading of the standard. The grammar is ambiguous in many places.)
EDIT: From the bash manual:
The following list is what’s changed when ‘POSIX mode’ is in effect:
...
Process substitution is not available.

How to display column headers for 'ls -l' command in unix/linux?

I want to display all the column headers when I type ls -l command in bash shell in unix/linux
When we type ls -ltr on command prompt we get something like the following.
-r--r--r-- 2 makerpm root 1898 Jan 28 14:52 sample3
-r--r--r-- 2 makerpm root 1898 Jan 28 14:52 sample1
What I want is to know whether ls has any options to display with column headers:
File_Permissions Owner Group Size Modified_Time Name
-r--r--r-- 2 makerpm root 1898 Jan 28 14:52 sample3
-r--r--r-- 2 makerpm root 1898 Jan 28 14:52 sample1
exa is a replacement/enhancement for ls. If you pass on the arguments -lh with exa, it will include a header row printing the column names like so:
exa -lh
Example output:
Permissions Size User Date Modified Name
.rwx------ 19 username 29 Sep 11:25 dont_cra.sh
drw-r----- - username 29 Sep 11:26 f1
.rw-r--r--# 811k username 29 Sep 11:25 row_count.dat
.rw-r--r-- 54 username 29 Sep 11:25 some_text.txt
You can set up an alias in .bashrc that replaces ls with exa.
The last answer using sed was slick, but unfortunately, if you have color added to your output (which most people do) it removes all color. I would like to suggest a better way, and ironically, simpler too.
First off, my .bashrc USED to have the following:
# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
fi
alias ls='ls -AFhls --color --group-directories-first'
To be honest, you don't need the dircolors part, that is just a little extra I use, you could have something as simple as:
alias ls='ls --color=auto'
I wanted column headers too, Googled it, and wound up here. However after I tried what the previous user suggested, with sed and realizing everything was white, and my colors have all gone away.
That's when I tried something different in my .bashrc file, and it worked.
Simply alias ls, echo first, then place a semi-colon, then your ls command.
My .bashrc file now has the following line.
alias ls='echo "Dir Size|Perms|Link Count|Owner|Group|Size|Mod. Time|Name"; ls -AFhls --color --group-directories-first'
When doing it this way, utilizing echo instead of sed, all colors continue to work.
Not sure if this is specific to my terminal's output but, this worked for me.
And this is the output it yields. As you can see, it preserves color using this method. Just be sure to keep the ${1} variable in double quotes so files and directories with spaces in the name won't cause an error.
Here is the code so you can copy and paste for testing.
long_ls() {
local VAR="Permissions|Owner|Group|Size|Modified|Name"
if [ ! "${1}" ]; then
echo -e "$VAR" | column -t -s"|" && ls -l
else
echo -e "$VAR" | column -t -s"|" && ls -l "${1}"
fi
}
alias lls=$"long_ls ${1}"

How do I find the latest date folder in a directory and then construct the command in a shell script?

I have a directory in which I will have some folders with date format (YYYYMMDD) as shown below -
david#machineX:/database/batch/snapshot$ ls -lt
drwxr-xr-x 2 app kyte 86016 Oct 25 05:19 20141023
drwxr-xr-x 2 app kyte 73728 Oct 18 00:21 20141016
drwxr-xr-x 2 app kyte 73728 Oct 9 22:23 20141009
drwxr-xr-x 2 app kyte 81920 Oct 4 03:11 20141002
Now I need to extract latest date folder from the /database/batch/snapshot directory and then construct the command in my shell script like this -
./file_checker --directory /database/batch/snapshot/20141023/ --regex ".*.data" > shardfile_20141023.log
Below is my shell script -
#!/bin/bash
./file_checker --directory /database/batch/snapshot/20141023/ --regex ".*.data" > shardfile_20141023.log
# now I need to grep shardfile_20141023.log after above command is executed
How do I find the latest date folder and construct above command in a shell script?
Look, this is one of approaches, just grep only folders that have 8 digits:
ls -t1 | grep -P -e "\d{8}" | head -1
Or
ls -t1 | grep -E -e "[0-9]{8}" | head -1
You could try the following in your script:
pushd /database/batch/snapshot
LATESTDATE=`ls -d * | sort -n | tail -1`
popd
./file_checker --directory /database/batch/snapshot/${LATESTDATE}/ --regex ".*.data" > shardfile_${LATESTDATE}.log
See BashFAQ#099 aka "How can I get the newest (or oldest) file from a directory?".
That being said, if you don't care for actual modification time and just want to find the most recent directory based on name you can use an array and globbing (note: the sort order with globbing is subject to LC_COLLATE):
$ find
.
./20141002
./20141009
./20141016
./20141023
$ foo=( * )
$ echo "${foo[${#foo[#]}-1]}"
20141023

Using sed within "while read" expression

I am pretty stuck with that script.
#!/bin/bash
STARTDIR=$1
MNTDIR=/tmp/test/mnt
find $STARTDIR -type l |
while read file;
do
echo Found symlink file: $file
DIR=`sed 's|/\w*$||'`
MKDIR=${MNTDIR}${DIR}
mkdir -p $MKDIR
cp -L $file $MKDIR
done
I passing some directory to $1 parameter, this directory have three symbolic links. In while statement echoed only first match, after using sed I lost all other matches.
Look for output below:
[artyom#LBOX tmp]$ ls -lh /tmp/imp/
total 16K
lrwxrwxrwx 1 artyom adm 19 Aug 8 10:33 ok1 -> /tmp/imp/sym3/file1
lrwxrwxrwx 1 artyom adm 19 Aug 8 09:19 ok2 -> /tmp/imp/sym2/file2
lrwxrwxrwx 1 artyom adm 19 Aug 8 10:32 ok3 -> /tmp/imp/sym3/file3
[artyom#LBOX tmp]$ ./copy.sh /tmp/imp/
Found symlink file: /tmp/imp/ok1
[artyom#LBOX tmp]$
Can somebody help with that issue?
Thanks
You forgot to feed something to sed. Without explicit input, it reads nothing in this construction. I wouldn't use this approach anyway, but just use something like:
DIR=`dirname "$file"`

Add or update a configuration record in /etc/environment

My /etc/environment looks like this:
cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
I wish to use a command (sed, awk, python, whatever....) that will make it look like this:
cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
JAVA_HOME="/usr/lib/jvm/java-6-sun"
Now the catch is, I would rather it be a 1 liner (in the fields of sed -XYZ /DoMagic/ /etc/environment), it needs to contain merging logic that is - either appends a new configuration record or update an existing one. Bottom line, it should prevent the file from looking like this: (Caused by in experienced shell scripters calling echo >> on each invocation)
cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
JAVA_HOME="/usr/lib/jvm/java-5-sun"
JAVA_HOME="/usr/lib/jvm/java-6-sun"
JAVA_HOME="/usr/lib/jvm/java-6-sun"
JAVA_HOME="/usr/lib/jvm/java-6-sun"
I guess this is a trick questions, because what I'm trying to avoid using custom scripts, such as
/usr/local/bin/PropUpdate /etc/environment JAVA_HOME "/usr/lib/jvm/java-6-sun"
/usr/local/bin/PropUpdate is the following script (written for the sake of example, may contain bugs. Comments are appreciated)
#!/bin/bash
# Append/Update a configuration record in a file
#
# Usage example:
# /usr/local/bin/PropUpdate /etc/environment JAVA_HOME "/usr/lib/jvm/java-6-sun"
#
# Author Maxim Veksler <maxim#vekslers.org>
# Version 0.5-2010-07-27
EXPECTED_ARGS=3
E_BADARGS=3
E_BADFILE=4
if [[ $# -ne ${EXPECTED_ARGS} ]]; then
echo "Usage: `basename $0` /path/to/config.conf ParameterName newValueText" >&2
exit $E_BADARGS
fi
CONFIGURATION_FILE="$1"
CONFIGURATION_PARAMETER="$2"
CONFIGURATION_VALUE="$3"
if [[ ! -e "${CONFIGURATION_FILE}" ]]; then
echo "Configuration file ${CONFIGURATION_FILE} does not exist" >&2
exit $E_BADFILE
fi
if [[ ! -w "${CONFIGURATION_FILE}" ]]; then
echo "Can't modify ${CONFIGURATION_FILE}" >&2
exit $E_BADFILE
fi
#########################################
## Decide what parameter we are adding ##
#########################################
__param_found=0
# First check CONFIGURATION_PARAMETER supplied by use that contains "="
if [[ ${CONFIGURATION_PARAMETER} == *=* ]]; then
# It should exist in the file, plain
if grep -qE "^${CONFIGURATION_PARAMETER}" "${CONFIGURATION_FILE}"; then
__param_found=1
SUFFIX_REGEX='[[:space:]]*'
fi
else
# OK, sophisticated user, did not send "=" with the parameter...
if grep -qE "^${CONFIGURATION_PARAMETER}[[:space:]]*=" "${CONFIGURATION_FILE}"; then
# Let's check if such configuration with Parameter + "=" exists
__param_found=1
SUFFIX_REGEX='[[:space:]]*=[[:space:]]*'
elif grep -qE "^${CONFIGURATION_PARAMETER}[[:space:]]+" "${CONFIGURATION_FILE}"; then
# If such parameter exists, at all
__param_found=1
SUFFIX_REGEX='[[:space:]]\+'
fi
fi
if [[ $__param_found == 1 ]]; then
#echo sed -i "s|^\(${CONFIGURATION_PARAMETER}${SUFFIX_REGEX}\).*$|\1${CONFIGURATION_VALUE}|g" "${CONFIGURATION_FILE}"
sed -i "s|^\(${CONFIGURATION_PARAMETER}${SUFFIX_REGEX}\).*$|\1${CONFIGURATION_VALUE}|g" "${CONFIGURATION_FILE}"
else
if [[ ${CONFIGURATION_PARAMETER} == *=* ]]; then
# Configuration parameter contains "=" in it's name, good just append
echo "${CONFIGURATION_PARAMETER}${CONFIGURATION_VALUE}" >> "${CONFIGURATION_FILE}"
else
# Try to guess if this file is a "param = value" or "param value" type of file.
if grep -qE "^[[:alnum:]]+[[:space:]]*=" "${CONFIGURATION_FILE}"; then
# Seems like a "param = value" type of file
echo "${CONFIGURATION_PARAMETER}=${CONFIGURATION_VALUE}" >> "${CONFIGURATION_FILE}"
else
# Seems like a "param value" type of file
echo "${CONFIGURATION_PARAMETER} ${CONFIGURATION_VALUE}" >> "${CONFIGURATION_FILE}"
fi
fi
fi
#cat $CONFIGURATION_FILE
Thank you,
Maxim.
-- Update: I actually kinda liked this script, so I've improved it a bit. It now seems to be production ready. Enjoy.
Instead of trying to parse /etc/environment file, you could instead create a file with your own name in /etc/profile.d/, as I described in my answer to a relevant question. Then you could just copy it over during installation, because it contains just your content. Let alone that it will make your scripts shorter.
grep -q JAVA_HOME /etc/environment || echo 'JAVA_HOME="/usr/lib/jvm/java-5-sun"' >> /etc/environment
The grep command returns 0 (true) if the pattern is found in the file. So, the above reads:
check if JAVA_HOME is set in the file
OR set JAVA_HOME in the file
0-15:49 root#noneedto ~# cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
0-15:49 root#noneedto ~# grep JAVA_HOME /etc/environment && echo true
1-15:49 root#noneedto ~# grep -q JAVA_HOME /etc/environment || echo 'JAVA_HOME="/usr/lib/jvm/java-5-sun"' >> /etc/environment
0-15:49 root#noneedto ~# grep JAVA_HOME /etc/environment && echo true
JAVA_HOME="/usr/lib/jvm/java-5-sun"
true
0-15:49 root#noneedto ~# grep -q JAVA_HOME /etc/environment || echo 'JAVA_HOME="/usr/lib/jvm/java-5-sun"' >> /etc/environment
0-15:49 root#noneedto ~# cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
JAVA_HOME="/usr/lib/jvm/java-5-sun"
As you can see, if you invoke this one-liner multiple times, subsequent invocations do not add to the file because grep returns true before you attempt to append the file.
In my Ubuntu system, my JAVA_HOME looks like this:
JAVA_HOME=/usr/lib/jvm/default-java
Looking at that file with ls -l /usr/lib/jvm/default-java I noticed this:
lrwxrwxrwx 1 root root 24 Apr 27 2012 /usr/lib/jvm/default-java -> java-1.7.0-openjdk-amd64
In other words, the path in the soft link is the only thing you have to change.
To see the list of installed Java environments, I used this ls -l ... command:
prompt$ ls -l /usr/lib/jvm
total 20
lrwxrwxrwx 1 root root 24 Apr 27 2012 default-java -> java-1.7.0-openjdk-amd64
drwxr-xr-x 4 root root 4096 Feb 23 17:54 java-1.5.0-gcj-4.8-amd64
lrwxrwxrwx 1 root root 20 Sep 2 2012 java-1.6.0-openjdk-amd64 -> java-6-openjdk-amd64
lrwxrwxrwx 1 root root 20 Jul 3 2013 java-1.7.0-openjdk-amd64 -> java-7-openjdk-amd64
drwxr-xr-x 5 root root 4096 Oct 7 2012 java-6-openjdk-amd64
drwxr-xr-x 3 root root 4096 Oct 7 2012 java-6-openjdk-common
drwxr-xr-x 5 root root 4096 Sep 21 20:06 java-7-openjdk-amd64
drwxr-xr-x 8 root root 4096 Sep 18 21:18 java-7-oracle
So now I can switch to another default with:
sudo rm /usr/lib/jvm/default-java
sudo ln -s java-7-oracle /usr/lib/jvm/default-java
And the JAVA_HOME variable will run Java 7 from Oracle.

Resources