How to escape double quotes in bash command - bash

I have the following command, which I am trying to append to my ~/.bash_profile:
echo 'alias N="cd $(pwd) && source ./bin/activate && cd new""
I want it to return echo:
alias N="cd /Users/david/Desktop/django2 && source ./bin/activate && cd new"
What would be the correct way to escape the pwd or quotes?

This should work for you:
echo "alias N='cd $(pwd) && source ./bin/activate && cd new'"
However recommend you to use function instead of alias.
echo "N() { cd '$PWD' && source ./bin/activate && cd new; }"
Note use of $PWD instead of command pwd

To echo a literal " inside of a double-quoted string, use the escape character \:
echo "alias N=\"cd $(pwd) && source ./bin/activate && cd new\""
Outputs on my box:
alias N="cd /home/bishop/ && source ./bin/activate && cd new"
However, your use case suggests to me a function:
go() {
local where
where=${1:-${PWD}}
cd "${where}" && source ./bin/activate && cd new
}

Avoid excessive escaping with a here document:
cat >> ~/.bash_profile << EOF
alias N="cd $(pwd) && source ./bin/activate && cd new"
EOF

Related

Avoid $var to be interpreted as a variable inside inverted commas

I have tried in many ways but couldn't get it the right way.
fun="
mkcdo ()
{
mkdir -p -- \"'$1'\" && cd -P -- \"'$1'\"
}"
echo "$fun" >> ~/.bashrc
What I want is to append this in .bashrc
mkcd ()
{
mkdir -p -- "$1" && cd -P -- "$1"
}
Could that be done? Is there a way in bash like there is in python: r'whatever\you\$write' so that it is completely ignored as simple text?
Using a variable to store a bash function code sounds much of anti-pattern. For multi-line formatted strings, I would recommend using here-doc and quote them to avoid expanding the variable,
cat >> ~/.bashrc << 'EOF'
mkcd () {
mkdir -p -- "$1" && cd -P -- "$1"
}
EOF
Further reading - Bash - Here documents
Alternative1: just use single quotes.
fun='
mkcdo ()
{
mkdir -p -- "$1" && cd -P -- "$1"
}'
echo "$fun" >> ~/.bashrc
Alternative2: escape the $ sign.
fun="
mkcdo ()
{
mkdir -p -- \"\$1\" && cd -P -- \"\$1\"
}"
echo "$fun" >> ~/.bashrc

Using shell scripts to remove clearcase view if it exists using one liner if

I want to test if a clearcase view exists and execute the remove command only if it exists. I am trying to do it from a shell script in a Linux 6.x environment. I tried to format my conditions both as one-liner as well as full "if" statement, but don't seem to be able to get it to work. What do I need to do to get both - one-liner and full if syntax - approaches working?
Here is the code, in its latest state
#!/bin/ksh
#
STREAMNAME=app_stream_int
PVOB=domain_pvob
VOB=domain_app
viewdir=/opt/local/software/rational/viewstorage
shareddir=/opt/shared/test
storagedir=${shareddir}/viewstorage
projectdir=${shareddir}/projects
ctdir=/opt/rational/clearcase/bin
viewname=$viewdir/test_$STREAMNAME.vws
viewtag=test_$STREAMNAME
echo "STREAMNAME $STREAMNAME - PVOB $PVOB - VOB $VOB"
echo "Removing View if it exists ... \n"
# [ $(${ctdir}/cleartool lsview ${viewname}) ] && { ${ctdir}/cleartool rmview ${viewname}; echo "view removed" }
# [ ${ctdir}/cleartool lsview -long ${viewtag} ] && { ${ctdir}/cleartool rmview ${viewname}; echo "view removed" }
# ${ctdir}/cleartool lsview -long ${viewtag} | grep "Tag" && { ${ctdir}/cleartool rmview ${viewname}; echo "view removed" }
if [ ${ctdir}/cleartool lsview -long ${viewtag} | grep 'Tag' == "0" ]
then
echo "view found"
${ctdir}/cleartool rmview ${viewname}
fi
I would prefer a one-liner type of solution, but 'if' statement will also work.
Provided the command follows the UNIX-convention of exit codes, the general one-liner looks like:
command && { success1; success2; } || { failure1; failure2; }
The list following && specifies what should run when command succeeds (exits with 0), while the list following || specifies what should run when the command fails. In the list, note that all the commands end in a semi-colon, including the last one.
For your specific case, this looks like it'll work:
"${ctdir}"/cleartool lsview "${viewname}" && { "${ctdir}"/cleartool rmview "${viewname}" && echo "view removed" || echo "cannot remove view"; }
Here is an example of this pattern in action, using standard commands:
$ ls foo && { rm -f foo && echo 'removed' || echo 'not removed'; }
ls: cannot access foo: No such file or directory
$ touch foo
$ ls foo && { rm -f foo && echo 'removed' || echo 'not removed'; }
foo
removed
$ sudo touch /foo
$ sudo chmod 600 /foo
$ ls /foo && { rm -f /foo && echo 'removed' || echo 'not removed'; }
/foo
rm: cannot remove ‘/foo’: Permission denied
not removed

Underlining to bash prompt only when pwd changed

I want to underline to my bash shell prompt(=PS1) only when a current directory is changed.
I tried this.
At .bashrc file I wrote
DIR_CHANGED=
function cd {
builtin cd "$#"
DIR_CHANGED=1
}
function dir_ul {
# if $DIR_CHANGED is 1, draw underline
if [ x == x$DIR_CHANGED ]; then echo -en '\033[0;34m'; else echo -en '\033[4;34m'; fi
export DIR_CHANGED=''
}
export PS1='$(dir_ul)\w$(tput sgr0)$ '
But not worked.
How do I fix?
I hate to say it, but I'd call that a bash bug. Here's a workaround: use PROMPT_COMMAND to
copy $DIR_CHANGED and reset it, and in dir_ul refer to that saved copy. Minimal changes:
function dir_ul {
if [ x == x$DIR2 ]; then echo -en '\033[0;34m'; else echo -en '\033[4;34m'; fi
}
PROMPT_COMMAND='DIR2=$DIR_CHANGED;DIR_CHANGED='
Test this in your ~/.bashrc with a second shell:
PREV="$PWD"
PROMPT_COMMAND='[[ $PREV != $PWD ]] && PS1="$(tput smul)\w$(tput rmul)$ " && PREV="$PWD" || PS1="$(tput rmul)\w$ "'
The $(dir_ul) gets evaluated at the instance you set the PS1 variable, rather than being continuously updated.

cd that manipulates only one part of the path at the beginning in Bash (like ZSH)

Is there any way to reproduce the behaviour of cd in the zsh shell in bash?
http://zshwiki.org/home/builtin/cd
The cd in Bash can also change some directories at some lower levels:
% pwd
~/data/foo/horrible/dir/names
% cd foo bar
% pwd
~/data/bar/horrible/dir/names
You can create your own cd that implements the functionality you want and then calls the builtin.
cd() {
if (( $# == 2 )) && [[ "$1" != -* ]]
then
builtin cd "${PWD/$1/$2}"
else
builtin cd "$#"
fi
}

How to get absolute path name of shell script on MacOS?

readlink -f does not exist on MacOS. The only working solution for Mac OS I managed to find on the net goes like this:
if [[ $(echo $0 | awk '/^\//') == $0 ]]; then
ABSPATH=$(dirname $0)
else
ABSPATH=$PWD/$(dirname $0)
fi
Can anyone suggest anything more elegant to this seemingly trivial task?
Another (also rather ugly) option:
ABSPATH=$(cd "$(dirname "$0")"; pwd -P)
From pwd man page,
-P Display the physical current working directory (all symbolic links resolved).
Get absolute path of shell script
Dug out some old scripts from my .bashrc, and updated the syntax a bit, added a test suite.
Supports
source ./script (When called by the . dot operator)
Absolute path /path/to/script
Relative path like ./script
/path/dir1/../dir2/dir3/../script
When called from symlink
When symlink is nested eg) foo->dir1/dir2/bar bar->./../doe doe->script
When caller changes the scripts name
It has been tested and used in real projects with success, however there may be corner cases I am not aware of.
If you were able to find such a situation, please let me know.
(For one, I know that this does not run on the sh shell)
Code
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]) do
cd "`dirname "${SCRIPT_PATH}"`"
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd =[`pwd`]"
Known issuse
Script must be on disk somewhere, let it be over a network.
If you try to run this script from a PIPE it will not work
wget -o /dev/null -O - http://host.domain/dir/script.sh |bash
Technically speaking, it is undefined.
Practically speaking, there is no sane way to detect this.
Test case used
And the current test case that check that it works.
#!/bin/bash
# setup test enviroment
mkdir -p dir1/dir2
mkdir -p dir3/dir4
ln -s ./dir1/dir2/foo bar
ln -s ./../../dir3/dir4/test.sh dir1/dir2/foo
ln -s ./dir1/dir2/foo2 bar2
ln -s ./../../dir3/dir4/doe dir1/dir2/foo2
cp test.sh ./dir1/dir2/
cp test.sh ./dir3/dir4/
cp test.sh ./dir3/dir4/doe
P="`pwd`"
echo "--- 01"
echo "base =[${P}]" && ./test.sh
echo "--- 02"
echo "base =[${P}]" && `pwd`/test.sh
echo "--- 03"
echo "base =[${P}]" && ./dir1/dir2/../../test.sh
echo "--- 04"
echo "base =[${P}/dir3/dir4]" && ./bar
echo "--- 05"
echo "base =[${P}/dir3/dir4]" && ./bar2
echo "--- 06"
echo "base =[${P}/dir3/dir4]" && `pwd`/bar
echo "--- 07"
echo "base =[${P}/dir3/dir4]" && `pwd`/bar2
echo "--- 08"
echo "base =[${P}/dir1/dir2]" && `pwd`/dir3/dir4/../../dir1/dir2/test.sh
echo "--- 09"
echo "base =[${P}/dir1/dir2]" && ./dir1/dir2/test.sh
echo "--- 10"
echo "base =[${P}/dir3/dir4]" && ./dir3/dir4/doe
echo "--- 11"
echo "base =[${P}/dir3/dir4]" && ./dir3/dir4/test.sh
echo "--- 12"
echo "base =[${P}/dir3/dir4]" && `pwd`/dir3/dir4/doe
echo "--- 13"
echo "base =[${P}/dir3/dir4]" && `pwd`/dir3/dir4/test.sh
echo "--- 14"
echo "base =[${P}/dir3/dir4]" && `pwd`/dir1/dir2/../../dir3/dir4/doe
echo "--- 15"
echo "base =[${P}/dir3/dir4]" && `pwd`/dir1/dir2/../../dir3/dir4/test.sh
echo "--- 16"
echo "base s=[${P}]" && source test.sh
echo "--- 17"
echo "base s=[${P}]" && source `pwd`/test.sh
echo "--- 18"
echo "base s=[${P}/dir1/dir2]" && source ./dir1/dir2/test.sh
echo "--- 19"
echo "base s=[${P}/dir3/dir4]" && source ./dir1/dir2/../../dir3/dir4/test.sh
echo "--- 20"
echo "base s=[${P}/dir3/dir4]" && source `pwd`/dir1/dir2/../../dir3/dir4/test.sh
echo "--- 21"
pushd . >/dev/null
cd ..
echo "base x=[${P}/dir3/dir4]"
./`basename "${P}"`/bar
popd >/dev/null
PurpleFox aka GreenFox
Also note that homebrew's (http://brew.sh) coreutils package includes realpath (link created in/opt/local/bin).
$ realpath bin
/Users/nhed/bin
Using bash I suggest this approach. You first cd to the directory, then you take the current directory using pwd. After that you must return to the old directory to ensure your script does not create side effects to an other script calling it.
cd "$(dirname -- "$0")"
dir="$PWD"
echo "$dir"
cd - > /dev/null
This solution is safe with complex path. You will never have troubles with spaces or special charaters if you put the quotes.
Note: the /dev/null is require or "cd -" print the path its return to.
If you don't mind using perl:
ABSPATH=$(perl -MCwd=realpath -e "print realpath '$0'")
Can you try something like this inside your script?
echo $(pwd)/"$0"
In my machine it shows:
/home/barun/codes/ns2/link_down/./test.sh
which is the absolute path name of the shell script.
I've found this to be useful for symlinks / dynamic links - works with GNU readlink only though (because of the -f flag):
# detect if GNU readlink is available on OS X
if [ "$(uname)" = "Darwin" ]; then
which greadlink > /dev/null || {
printf 'GNU readlink not found\n'
exit 1
}
alias readlink="greadlink"
fi
# create a $dirname variable that contains the file dir
dirname=$(dirname "$(readlink -f "$0")")
# use $dirname to find a relative file
cat "$dirname"/foo/bar.txt
this is what I use, may need a tweak here or there
abspath ()
{
case "${1}" in
[./]*)
local ABSPATH="$(cd ${1%/*}; pwd)/${1##*/}"
echo "${ABSPATH/\/\///}"
;;
*)
echo "${PWD}/${1}"
;;
esac
}
This is for any file - and of curse you can just invoke it as abspath ${0}
The first case deals with relative paths by cd-ing to the path and letting pwd figure it out
The second case is for dealing with a local file (where the ${1##/} would not have worked)
This does NOT attempt to undo symlinks!
This works as long as it's not a symlink, and is perhaps marginally less ugly:
ABSPATH=$(dirname $(pwd -P $0)/${0#\.\/})
If you're using ksh, the ${.sh.file} parameter is set to the absolute pathname of the script. To get the parent directory of the script: ${.sh.file%/*}
I use the function below to emulate "readlink -f" for scripts that have to run on both linux and Mac OS X.
#!/bin/bash
# This was re-worked on 2018-10-26 after der#build correctly
# observed that the previous version did not work.
# Works on both linux and Mac OS X.
# The "pwd -P" re-interprets all symlinks.
function read-link() {
local path=$1
if [ -d $path ] ; then
local abspath=$(cd $path; pwd -P)
else
local prefix=$(cd $(dirname -- $path) ; pwd -P)
local suffix=$(basename $path)
local abspath="$prefix/$suffix"
fi
if [ -e $abspath ] ; then
echo $abspath
else
echo 'error: does not exist'
fi
}
# Example usage.
while (( $# )) ; do
printf '%-24s - ' "$1"
read-link $1
shift
done
This is the output for some common Mac OS X targets:
$ ./example.sh /usr/bin/which /bin/which /etc/racoon ~/Downloads
/usr/bin/which - /usr/bin/which
/bin/which - error: does not exist
/etc/racoon - /private/etc/racoon
/Users/jlinoff/Downloads - /Users/jlinoff/Downloads
The is the output for some linux targets.
$ ./example.sh /usr/bin/which /bin/whichx /etc/init.d ~/Downloads
/usr/bin/which - /usr/bin/which
/bin/whichx - error: does not exist
/etc/init.d - /etc/init.d
/home/jlinoff/Downloads - /home/jlinoff/Downloads

Resources