When I run the following script in bash:
#!/bin/bash
names=("ALL" "no_C" "no_R" "no_Q")
for name in $names; do
export name=$name
mkdir -p $name
( echo 'selection' 'System' | gmx cluster -f ${name}_protein_only.trr -s ${name}_protein_only.pdb -n ${name}_index.ndx -g ${name}/cluster.log -cutoff 0.2 -fit -method gromos -o ${name}/cluster.output -dist ${name}/rmsd-dist.xvg -av -cl ${name}/clusters.pdb ) &
done
wait
The for loop won't loop until the subshell has completed, even though I've put it into the background with '&'. If I run this same script in zsh, it runs as expected (4 parallel tasks). Is this a bug or am I missing something?
You need to use a different notation for all the elements of an array in Bash (see Arrays and Shell parameter expansion):
for name in "${name[#]}"; do
When you specify $name and name is an array, Bash treats it as ${name[0]}.
I used this variant on your code to demonstrate:
#!/bin/bash
names=("ALL" "no_C" "no_R" "no_Q")
for name in "${names[#]}"
do
export name=$name
mkdir -p $name
( echo $name 'selection' 'System' | sed s/s/z/g; sleep 3 ) &
done
wait
I believe what Jonathan Leffler wrote is correct in terms of the definition of a Bash array and how to loop through it.
But, if you wanted to define names as a series of strings like this:
names="ALL no_C no_R no_Q"
Then you could still loop through it using:
for name in $names; do
.... do something with name
done
I'm writing a shell script that's meant to run on a range of machines. Some of these machines have bash 2 or bash 3. Some are running BusyBox 1.18.4 where bin/bash exists but
/bin/bash --version doesn't return anything at all
foo=( "hello" "world" ) complains about a syntax error near the unexpected "(" both with and without the extra spaces just inside the parens ... so arrays seem either limited or missing
There are also more modern or more fully featured Linux and bash versions.
What is the most portable way for a bash script to build up arguments at run time for calling some utility like find? I can build up a string but feel that arrays would be a better choice. Except there's that second bullet point above...
Let's say my script is foo and you call it like so: foo -o 1 .jpg .png
Here's some pseudo-code
#!/bin/bash
# handle option -o here
shift $(expr $OPTIND - 1)
# build up parameters for find here
parameters=(my-diretory -type f -maxdepth 2)
if [ -n "$1" ]; then
parameters+=-iname '*$1' -print
shift
fi
while [ $# -gt 0 ]; do
parameters+=-o -iname '*$1' -print
shift
done
find <new positional parameters here> | some-while-loop
If you need to use mostly-POSIX sh, such as would be available in busybox ash-named-bash, you can build up positional parameters directly with set
$ set -- hello
$ set -- "$#" world
$ printf '%s\n' "$#"
hello
world
For a more apt example:
$ set -- /etc -name '*b*'
$ set -- "$#" -type l -exec readlink {} +
$ find "$#"
/proc/mounts
While your question involves more than just Bash, you may benefit from reading the Wooledge Bash FAQ on the subject:
http://mywiki.wooledge.org/BashFAQ/050
It mentions the use of "set --" for older shells, but also gives a lot of background information. When building a list of argument, it's easy to create a system that works in simple cases but fails when the data has special characters, so reading up on the subject is probably worthwhile.
I have the following simple bash script:
#!/bin/bash -fx
ls *sh
The problem is that bash add a quote to the pattern and I get wrong output.
+ ls '*sh'
ls: cannot access *sh: No such file or directory
How can I change this behavior?
The output of ls *sh from the terminal is:
$ls *sh
a.bash a.sh b.sh
I tried to add quotes according to this post - "Bash variable containing file wildcard"
without success
That because you're disabling pathname expansion with the -f option.
#!/bin/bash -fx
From man:
-f
Disable filename expansion (globbing).
The following almost works:
#!/bin/bash
/* 2>&1 >/dev/null
script_dir=$(dirname "$0")
export GROOVY_HOME=${script_dir}/../../../../Tools/groovy/groovy-2.0.2
exec ${GROOVY_HOME}/bin/groovy -cp "${script_dir}:$(ls ${script_dir}/build/lib/runtime/*.jar | xargs echo | sed -e 's| |:|g')" "$0"
*/ // 2>&1 >/dev/null
println("aoeu")
The only problem is that the shell globs /* and tries to execute it. In the end, all I really want to do is to be able to build the Groovy script's classpath without having to have two separate scripts.
You can also try the technique below, which is independent of Groovy syntax (and does not normally produce output on stderr):
#!/bin/sh
script_dir=$(dirname "$0")
export GROOVY_HOME="$script_dir/../../../../Tools/groovy/groovy-2.0.2"
awk 'mark_on{print}/^__END__$/{mark_on=1}' "$0" >/tmp/$$.groovy
"$GROOVY_HOME/bin/groovy" -cp "$script_dir:$(echo "$script_dir"/build/lib/runtime/*.jar | tr " " :)" /tmp/$$.groovy
status=$?
rm -f /tmp/$$.groovy
exit $status
__END__
println("aoeu")
Also notice the simplification in the classpath calculation; remember that globbing (wildcard expansion) is performed by the shell, not by the command that takes the arguments, so you do not have to (nor do you want to) use ls in this case.
The curly brackets ${} in your original code are technically superfluous in this case (they are purely stylistic); they would be needed if you had for example to append a string directly after a variable substitution where there is no clear break between the variable name and what follows, e.g. you cannot say $my_varsome_string but you can write ${my_var}some_string, or any of $my_var"some_string" or $my_var'some_string' or "$my_var"some_string or "$my_var""some_string". I removed the braces for "minimalistic" purposes and in order to illustrate the above, but again, it's perfectly fine to keep them for stylistic reasons.
The quotes I added consistently in the code above protect you from potential blanks and certain other special characters inside $GROOVY_HOME. Feel free to remove them in order to simplify quoting (and be minimalistic) if you know $GROOVY_HOME will not contain blanks.
It seems you want to write a groovy / shell polyglot.
I don't know groovy, but from the documentation it seems [] is a valid groovy command, creating an empty list.
Then you could write it as follow:
#!/bin/bash
[ /* 2> /dev/null > /dev/null
script_dir=$(dirname "$0")
export GROOVY_HOME=${script_dir}/../../../../Tools/groovy/groovy-2.0.2
exec ${GROOVY_HOME}/bin/groovy -cp "${script_dir}:$(ls ${script_dir}/build/lib/runtime/*.jar | xargs echo | sed -e 's| |:|g')" "$0"
*/
]
println("aoeu")
Groovy will read [] and ignore it, and bash will call [ with /*, which will cause an error that is ignored. But it will not run any unexpected programs.
#!/bin/bash
script_dir="$(cd $(dirname $0) >/dev/null; pwd -P)"
function after-bangshe() {
sed -e '1,/^!#$/d' "$1"
}
if [ -z "${GROOVY_HOME}" ]
then
echo 'GROOVY_HOME must be defined.' >&2
exit 1
fi
CLASSPATH="${script_dir}" "${GROOVY_HOME}/bin/groovy" -e "$(after-bangshe $0)" "$#"
exit
!#
println 'aoeu'
The output of cd is redirected to /dev/null in case CDPATH is set (which makes cd noisy).
CLASSPATH is set to the script directory so that any support classes can be found.
The sed command strips out everything after the !# line.
How could I retrieve the current working directory/folder name in a bash script, or even better, just a terminal command.
pwd gives the full path of the current working directory, e.g. /opt/local/bin but I only want bin.
No need for basename, and especially no need for a subshell running pwd (which adds an extra, and expensive, fork operation); the shell can do this internally using parameter expansion:
result=${PWD##*/} # to assign to a variable
result=${result:-/} # to correct for the case where PWD=/
printf '%s\n' "${PWD##*/}" # to print to stdout
# ...more robust than echo for unusual names
# (consider a directory named -e or -n)
printf '%q\n' "${PWD##*/}" # to print to stdout, quoted for use as shell input
# ...useful to make hidden characters readable.
Note that if you're applying this technique in other circumstances (not PWD, but some other variable holding a directory name), you might need to trim any trailing slashes. The below uses bash's extglob support to work even with multiple trailing slashes:
dirname=/path/to/somewhere//
shopt -s extglob # enable +(...) glob syntax
result=${dirname%%+(/)} # trim however many trailing slashes exist
result=${result##*/} # remove everything before the last / that still remains
result=${result:-/} # correct for dirname=/ case
printf '%s\n' "$result"
Alternatively, without extglob:
dirname="/path/to/somewhere//"
result="${dirname%"${dirname##*[!/]}"}" # extglob-free multi-trailing-/ trim
result="${result##*/}" # remove everything before the last /
result=${result:-/} # correct for dirname=/ case
Use the basename program. For your case:
% basename "$PWD"
bin
$ echo "${PWD##*/}"
Use:
basename "$PWD"
OR
IFS=/
var=($PWD)
echo ${var[-1]}
Turn the Internal Filename Separator (IFS) back to space.
IFS=
There is one space after the IFS.
You can use a combination of pwd and basename. E.g.
#!/bin/bash
CURRENT=`pwd`
BASENAME=`basename "$CURRENT"`
echo "$BASENAME"
exit;
How about grep:
pwd | grep -o '[^/]*$'
This thread is great! Here is one more flavor:
pwd | awk -F / '{print $NF}'
basename $(pwd)
or
echo "$(basename $(pwd))"
I like the selected answer (Charles Duffy), but be careful if you are in a symlinked dir and you want the name of the target dir. Unfortunately I don't think it can be done in a single parameter expansion expression, perhaps I'm mistaken. This should work:
target_PWD=$(readlink -f .)
echo ${target_PWD##*/}
To see this, an experiment:
cd foo
ln -s . bar
echo ${PWD##*/}
reports "bar"
DIRNAME
To show the leading directories of a path (without incurring a fork-exec of /usr/bin/dirname):
echo ${target_PWD%/*}
This will e.g. transform foo/bar/baz -> foo/bar
echo "$PWD" | sed 's!.*/!!'
If you are using Bourne shell or ${PWD##*/} is not available.
Surprisingly, no one mentioned this alternative that uses only built-in bash commands:
i="$IFS";IFS='/';set -f;p=($PWD);set +f;IFS="$i";echo "${p[-1]}"
As an added bonus you can easily obtain the name of the parent directory with:
[ "${#p[#]}" -gt 1 ] && echo "${p[-2]}"
These will work on Bash 4.3-alpha or newer.
There are a lots way of doing that I particularly liked Charles way because it avoid a new process, but before know this I solved it with awk
pwd | awk -F/ '{print $NF}'
For the find jockeys out there like me:
find $PWD -maxdepth 0 -printf "%f\n"
i usually use this in sh scripts
SCRIPTSRC=`readlink -f "$0" || echo "$0"`
RUN_PATH=`dirname "${SCRIPTSRC}" || echo .`
echo "Running from ${RUN_PATH}"
...
cd ${RUN_PATH}/subfolder
you can use this to automate things ...
Just use:
pwd | xargs basename
or
basename "`pwd`"
Below grep with regex is also working,
>pwd | grep -o "\w*-*$"
If you want to see only the current directory in the bash prompt region, you can edit .bashrc file in ~. Change \w to \W in the line:
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
Run source ~/.bashrc and it will only display the directory name in the prompt region.
Ref: https://superuser.com/questions/60555/show-only-current-directory-name-not-full-path-on-bash-prompt
I strongly prefer using gbasename, which is part of GNU coreutils.
Just run the following command line:
basename $(pwd)
If you want to copy that name:
basename $(pwd) | xclip -selection clipboard
An alternative to basname examples
pwd | grep -o "[^/]*$"
OR
pwd | ack -o "[^/]+$"
My shell did not come with the basename package and I tend to avoid downloading packages if there are ways around it.
You can use the basename utility which deletes any prefix ending in / and the suffix (if present in string) from string, and prints the
result on the standard output.
$basename <path-of-directory>
Just remove any character until a / (or \, if you're on Windows). As the match is gonna be made greedy it will remove everything until the last /:
pwd | sed 's/.*\///g'
In your case the result is as expected:
λ a='/opt/local/bin'
λ echo $a | sed 's/.*\///g'
bin
Here's a simple alias for it:
alias name='basename $( pwd )'
After putting that in your ~/.zshrc or ~/.bashrc file and sourcing it (ex: source ~/.zshrc), then you can simply run name to print out the current directories name.
The following commands will result in printing your current working directory in a bash script.
pushd .
CURRENT_DIR="`cd $1; pwd`"
popd
echo $CURRENT_DIR