Given this script
# cat foo.sh
echo $HOME
set -x
mkdir ~
I am getting this result
# ./foo.sh
/home/Steven Penny
++ mkdir ''
mkdir: cannot create directory `': No such file or directory
If I run mkdir "$HOME" I have no problem. I think the issue is the space in the path, but can someone shed some light on this?
Looking at your debug output
++ mkdir ''
looks like "~" in mkdir ~ is expanding to nothing, thus causing mkdir to fail. As mkdir $HOME works fine it means you have enough space in the disk.
Can you check if echo ~ is displaying your home directory.
The problem was that the /etc/passwd file was missing.
To elaborate, even one this simple will work
Steven Penny::0:0::/home/Steven Penny
Related
I want to translate this bash-script intro a zsh-script. Hence I have no experience with this I hope I may get help here:
bash script:
SCRIPT_PATH="${BASH_SOURCE[0]}";
if([ -h "${SCRIPT_PATH}" ]) then
while([ -h "${SCRIPT_PATH}" ]) do SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
pushd . > /dev/null
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd > /dev/null
What I already know is that I can use
SCRIPT_PATH="$0"; to get the path were the script is located at. But then I get errors with the "readlink" statement.
Thanks for your help
Except for BASH_SOURCE I see no changes that you need to make. But what is the purpose of the script? If you want to get directory your script is located at there is ${0:A:h} (:A will resolve all symlinks, :h will truncate last path component leaving you with a directory name):
SCRIPT_PATH="${0:A:h}"
and that’s all. Note that original script has something strange going on:
if(…) and while(…) launch … in a subshell. You do not need subshell here, it is faster to do these checks using just if … and while ….
pushd . is not needed at all. While using pushd you normally replace the cd call with it:
pushd "$(dirname $SCRIPT_PATH)" >/dev/null
SCRIPT_PATH="$(pwd)"
popd >/dev/null
cd `…` will fail if … outputs something with spaces. It is possible for a directory to contain a space. In the above example I use "$(…)", "`…`" will also work.
You do not need trailing ; in variable declarations.
There is readlink -f that will resolve all symlinks thus you may consider reducing original script to SCRIPT_PATH="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))" (the behavior may change as your script seems to resolve symlinks only in last component): this is bash equivalent to ${0:A:h}.
if [ -h "$SCRIPT_PATH" ] is redundant since while body with the same condition will not be executed unless script path is a symlink.
readlink $SCRIPT_PATH will return symlink relative to the directory containing $SCRIPT_PATH. Thus original script cannot possibly used to resolve symlinks in last component.
There is no ; between if(…) and then. I am surprised bash accepts this.
All of the above statements apply both to bash and zsh.
If resolving only symlinks only in last component is essential you should write it like this:
SCRIPT_PATH="$0:a"
function ResolveLastComponent()
{
pushd "$1:h" >/dev/null
local R="$(readlink "$1")"
R="$R:a"
popd >/dev/null
echo $R
}
while test -h "$SCRIPT_PATH" ; do
SCRIPT_PATH="$(ResolveLastComponent "$SCRIPT_PATH")"
done
.
To illustrate 7th statement there is the following example:
Create directory $R/bash ($R is any directory, e.g. /tmp).
Put your script there without modifications, e.g. under name $R/bash/script_path.bash. Add line echo "$SCRIPT_PATH" at the end of it and line #!/bin/bash at the start for testing.
Make it executable: chmod +x $R/bash/script_path.bash.
Create a symlink to it: cd $R/bash && ln -s script_path.bash link.
cd $R
Launch $R/bash/1. Now you will see that your script outputs $R while it should output $R/bash like it does when you launch $R/bash/script_path.bash.
I wrote this little shell script(test.sh) to create a basic folder structure:
#!/bin/bash
# Check if directory already exists,
# if it doesnt, create one.
if [ ! -d "~/.dir1" ]; then
mkdir ".dir1"
else
rm -rf ".dir1"
mkdir ".dir1"
fi
When I run
test.sh
in console, the hidden folder is created.
But:
When I run it again it tells me:
mkdir: .dir1: File exists
But it could exist because I removed it in my shell script before I created a new one!
So why does it display this message?
Thanks and greetings!
Replace
[ ! -d "~/.dir1" ]
by
[ ! -d "${HOME}/.dir1" ]
I would simply use -p.
mkdir -p "$HOME/dir1"
If you pass -p, mkdir wouldn't throw an error if the directory already exists, it would simply silently return in that case.
If you want to make sure folder is empty use this:
rm -rf "$HOME/dir1"
mkdir -p "$HOME/dir1"
and no if! The basic problem with the if is the fact that it is not immune against race conditions. When the script went off from CPU right after the if - and creates "dir1" - your script will fail when it enters the CPU again since it still thinks the directory does not exist.
What you are doing by "~/.dir1" is not right. It's just another string for a directory name literally "~/.dir1" i.e ~ is not being expanded to $HOME.
Use full path or ~/".dir1" or ~/.dir1 instead.
You can use $HOME too: $HOME/.dir1 or "$HOME/.dir1" or "$HOME"/".dir1" all of them will produce same result... but quoting variables is a good practice.
~ isn't expanded when you place it in quotes. You need to leave it unquoted.
if [ ! -d ~/.dir1 ]
Of note, you're checking for ~/.dir1 but you make .dir1. That's only the same directory if the current directory is ~. If it isn't, they're not the same.
Also, mkdir -p will do this for you, creating a directory only if it doesn't exist already. You could simplify your script to:
mkdir -p ~/.dir1
or
rm -rf ~/.dir1
mkdir ~/.dir1
I want to translate this bash-script intro a zsh-script. Hence I have no experience with this I hope I may get help here:
bash script:
SCRIPT_PATH="${BASH_SOURCE[0]}";
if([ -h "${SCRIPT_PATH}" ]) then
while([ -h "${SCRIPT_PATH}" ]) do SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
pushd . > /dev/null
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd > /dev/null
What I already know is that I can use
SCRIPT_PATH="$0"; to get the path were the script is located at. But then I get errors with the "readlink" statement.
Thanks for your help
Except for BASH_SOURCE I see no changes that you need to make. But what is the purpose of the script? If you want to get directory your script is located at there is ${0:A:h} (:A will resolve all symlinks, :h will truncate last path component leaving you with a directory name):
SCRIPT_PATH="${0:A:h}"
and that’s all. Note that original script has something strange going on:
if(…) and while(…) launch … in a subshell. You do not need subshell here, it is faster to do these checks using just if … and while ….
pushd . is not needed at all. While using pushd you normally replace the cd call with it:
pushd "$(dirname $SCRIPT_PATH)" >/dev/null
SCRIPT_PATH="$(pwd)"
popd >/dev/null
cd `…` will fail if … outputs something with spaces. It is possible for a directory to contain a space. In the above example I use "$(…)", "`…`" will also work.
You do not need trailing ; in variable declarations.
There is readlink -f that will resolve all symlinks thus you may consider reducing original script to SCRIPT_PATH="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))" (the behavior may change as your script seems to resolve symlinks only in last component): this is bash equivalent to ${0:A:h}.
if [ -h "$SCRIPT_PATH" ] is redundant since while body with the same condition will not be executed unless script path is a symlink.
readlink $SCRIPT_PATH will return symlink relative to the directory containing $SCRIPT_PATH. Thus original script cannot possibly used to resolve symlinks in last component.
There is no ; between if(…) and then. I am surprised bash accepts this.
All of the above statements apply both to bash and zsh.
If resolving only symlinks only in last component is essential you should write it like this:
SCRIPT_PATH="$0:a"
function ResolveLastComponent()
{
pushd "$1:h" >/dev/null
local R="$(readlink "$1")"
R="$R:a"
popd >/dev/null
echo $R
}
while test -h "$SCRIPT_PATH" ; do
SCRIPT_PATH="$(ResolveLastComponent "$SCRIPT_PATH")"
done
.
To illustrate 7th statement there is the following example:
Create directory $R/bash ($R is any directory, e.g. /tmp).
Put your script there without modifications, e.g. under name $R/bash/script_path.bash. Add line echo "$SCRIPT_PATH" at the end of it and line #!/bin/bash at the start for testing.
Make it executable: chmod +x $R/bash/script_path.bash.
Create a symlink to it: cd $R/bash && ln -s script_path.bash link.
cd $R
Launch $R/bash/1. Now you will see that your script outputs $R while it should output $R/bash like it does when you launch $R/bash/script_path.bash.
I have a shell program in a directory (ie dir1/dothis.sh) - works fine when I cd to that directory and ./dothis.sh
if I created a ln to that directoy with a new name - dir2 and do dir2/dothis.sh
it would execute but it thinks the current execution path is the new dir where dir2 is pointing to at
in dothis.sh - how do I find where dothis.sh actually located? The problem I have is that the dir1/dothis.sh can be relocated from system to system so there is no warranty where dir1/dothis.sh can be hard code
Use the bash built-in
#!/bin/bash
echo "Current path: $PWD"
try this:
#!/bin/bash
echo $0
a=`pwd`
echo $a
b=$a"/"$0
echo `dirname $b`
How about
dirname $(readlink -f $0)
It will also resolve symbolic link if any...
I have larger shell script which handles different things.
It will get it's own location by the following...
BASEDIR=`dirname $0`/..
BASEDIR=`(cd "$BASEDIR"; pwd)`
then BASEDIR will be used create other variables like
REPO="$BASEDIR"/repo
But the problem is that this shell script does not work if the path contains spaces where it is currently executed.
So the question is: Does exist a good solution to solve that problem ?
Be sure to double-quote anything that may contain spaces:
BASEDIR="`dirname $0`"
BASEDIR="`(cd \"$BASEDIR\"; pwd)`"
The answer is "Quotes everywhere."
If the path you pass in has a space in it then dirname $0 will fail.
$ cat quote-test.sh
#!/bin/sh
test_dirname_noquote () {
printf 'no quotes: '
dirname $1
}
test_dirname_quote () {
printf 'quotes: '
dirname "$1"
}
test_dirname_noquote '/path/to/file with spaces/in.it'
test_dirname_quote '/path/to/file with spaces/in.it'
$ sh quote-test.sh
no quotes: usage: dirname string
quotes: /path/to/file with spaces
Also, try this fun example
#!/bin/sh
mkdir -p /tmp/foo/bar/baz
cd /tmp/foo
ln -s bar quux
cd quux
cat >>find-me.sh<<"."
#!/bin/sh
self_dir="$(dirname $0)"
base_dir="$( (cd "$self_dir/.." ; pwd -P) )"
repo="$base_dir/repo"
printf 'self: %s\n' "$self_dir"
printf 'base: %s\n' "$base_dir"
printf 'repo: %s\n' "$repo"
.
sh find-me.sh
rm -rf /tmp/foo
Result when you run it:
$ sh example.sh
self: .
base: /tmp/foo
repo: /tmp/foo/repo
Quote your full variable like this:
REPO="$BASEDIR/repo"
There is no reliable and/or portable way to do this correctly.
See How do I determine the location of my script? as to why
The best answer is the following, which is still OS dependent
BASEDIR=$(readlink -f $0)
Then you can do things like REPO="$BASEDIR"/repo , just be sure to quote your variables as you did.
Works perfectly fine for me. How are you using REPO? What specifically "doesn't work" for you?
I tested
#!/bin/sh
BASEDIR=`dirname $0`/..
BASEDIR=`(cd "$BASEDIR"; pwd)`
REPO="$BASEDIR"/repo
echo $REPO
in a ".../a b/c d" directory. It outputs ".../a b/repo", as expected.
Please give the specific error that you are receiving... A "doesn't work" bug report is the least useful bug report, and every programmer absolutely hates it.
Using spaces in directory names in unix is always an issue so if they can be avoided by using underscores, this prevents lots of strange scripting behaviour.
I'm unclear why you are setting BASEDIR to be the parent directory of the directory containing the current script (..) and then resetting it after changing into that directory
The path to the directory should still work if it has ..
e.g. /home/trevor/data/../repo
BASEDIR=`dirname $0`/..
I think if you echo out $REPO it should have the path correctly assigned because you used quotes when assigning it but if you then try to use $REPO somewhere else in the script, you will need to use double quotes around that too.
e.g.
#!/bin/ksh
BASEDIR=`dirname $0`/..
$REPO="$BASEDIR"/repo
if [ ! -d ["$REPO"]
then
echo "$REPO does not exist!"
fi
Use speech marks as below:
BASEDIR=`dirname "${0}"`/..