Shell subcommand output to parent command - bash

I have a one line command, lets say
grep needle haystack.file
What if i wanted to replace "needle" with the current working directory using the pwd command. Now it might be possible using pipes but I need the needle part to show the working directory and the rest of the command to be the same.
So preferably something like this:
grep (pwd) haystack.file
Which when executed would actually run the following command:
grep /var/www/html/ haystack.file
I've done a bit of searching and have found a lot of examples with pipes but it cant be applied in my scenario as the first part (grep) and second part (haystack.file) is fixed in an application.

Use the $PWD variable, always set:
grep "$PWD" haystack.file
You can also use command substitution:
grep "$(pwd)" haystack.file
Note the importance of quotes. Do it! Otherwise strange things can happen.

You can use command substitution
Test
$ echo $(pwd) > test
$ grep $(pwd) test
/home/xxx/yyy
OR
$ grep `pwd` test
/home/xxx/yyy
Security
It's always recomended to quote the command substituion to take care of the spaces in the output of pwd command
Test
$ pwd
/home/xxx/yyy/hello world
$(pwd) > test
$ grep $(pwd) test #without quoting
grep: world: No such file or directory
test:/home/xxx/yyy/hello world
$ grep "$(pwd)" test #with quoting
/home/xxx/yyy/hello world

Related

Loop over directories and act on files in bash script

I have a script, /home/user/me/my_script.sh that is supposed to loop over multiple directories and process files. My current working directory is /home/user/me. A call to ls -R yields:
./projects:
dir1 dir2 dir3
./projects/dir1:
image1.ntf points2.csv image1.img image1.hdr
./projects/dir2:
image2.ntf points2.csv image2.img image2.hdr
./projects/dir3:
image3.ntf points3.csv image3.img image3.hdr
I have this script:
#! /bin/bash -f
for $dir in $1*
do
echo $dir
set cmd = `/home/tools/tool.sh -i $dir/*.ntf -flag1 -flag2 -flag3 opt3`
$cmd
done
This is how it is run (from cwd /home/user/me) and the result:
bash-4.1$ ./myscript.sh projects/
projects/*
bash-4.1$
This is not the expected output.The expected output is:
bash-4.1$ ./myscript.sh projects/
projects/dir1
[output from tool.sh]
projects/dir2
[output from tool.sh]
projects/dir3
[output from tool.sh]
bash-4.1$
What should happen is the script should go into the first directory, find the *.ntf file and pass it to tool.sh. At that point I would start seeing output from that tool. I have run the tool on a single file:
bash-4.1$ /home/tools/tool.sh -i /home/user/me/projects/dir1/image1.ntf -flag1 -flag2 -flag3 opt3
[expected output from tool. lengthy.]
bash-4.1$
I have tried syntax found here: How to loop over directories in Linux? and here: Looping over directories in Bash
for $dir in /$1*/
do ...
Result:
bash-4.1$ ./myscript.sh projects/
/projects/*/
And:
for $dir in $1/*
do ...
Result:
bash-4.1$ ./myscript.sh projects
projects/*
I'm not sure how many other iterations of wildcard and slash I can come up with. What is the correct syntax?
First, you should remove flag -f in your shebang, because it utterly means:
$ man bash
[…]
-f Disable pathname expansion.
Second, there are some typical bug patterns: spaces missing around variables (write "$dir" to cope with directory names containing spaces), there is a spurious $ in your for line (write for dir in "$1"*) instead, the set line is incorrect (set is a shell builtin only used to change the configuration of the shell, e.g., set -x), according to your answer to #ghoti's question it seems that the $cmd line is unnecessary. Also, the backquotes syntax is deprecated and could have been replaced with cmd=$(/home/tools/tool.sh -i "$dir"/*.ntf -flag1 -flag2 -flag3 opt3).
This would lead to the following script:
#!/bin/bash
for dir in "$1"*
do
[[ -d "$dir" ]] || continue # only consider existing folders
printf "%s=%q\n" dir "$dir"
/home/tools/tool.sh -i "$dir"/*.ntf -flag1 -flag2 -flag3 opt3
done
As an aside, I would recommend to always run the ShellCheck static analyzer on your Bash scripts, in order to detect typical bugs and have feedback w.r.t. good practices. If you have a Linux distribution, it should be installable with your standard package manager.

Assign output of mkdir command to variable

I am trying to assign the output of mkdir command to a variable. So I can use the directory further.
-bash-4.1$ pwd
/user/ravi/myscripts/tmpdata
-bash-4.1$ OUTPUT=$(mkdir tmpbkp.`date +%F`)
-bash-4.1$ ls | grep tmp
tmpbkp.2017-04-06
-bash-4.1$ echo "$OUTPUT"
But the directory name is not assigning to the variable. Could you please correct me where I am wrong.
When you run the mkdir command by itself, look how much output it produces:
$ mkdir foo
$
None!
When you use a command substitution to generate the argument to mkdir, look how much extra output you get:
$ mkdir tmpbkp.`date +%F`
$
None!
When you put it inside $() it still produces no output.
There is a -v option for mkdir (in the GNU version at least) which produces some output, but it's probably not what you want.
You want the name of the directory in a variable? Put it in a variable first, then call mkdir.
$ thedir=tmpbkp.`date +%F`
$ mkdir $thedir
$ echo $thedir
tmpbkp.2017-04-06
$

Why the command in a file fails to work ? (FreeBSD 10.2)

https://www.youtube.com/watch?v=bu3_RzzEiVo
My intention is to experiment with shell scripts in a file. (FreeBSD 10.2)
I create a file named script.sh
cat > script.sh
set dir = `pwd`
echo The date today is `date`
echo The current directory is $dir
[Ctrl-d]
After giving it execution authority, I run the command
sh script.sh
I get
Why the directory is not displayed?
Then I make a change.
cat > script.sh
set dir = `pwd`
echo The date today is `date`
echo The current directory is `pwd`
[Ctrl-d]
This time , it works fine. The directory is shown successfully.
I would like to know why ? Could anyone tell me ?
The answer from TessellatingHeckler was on the right track.
From the man page for sh(1):
set [-/+abCEefIimnpTuVvx] [-/+o longname] [-c string] [-- arg ...]
The set command performs three different functions:
With no arguments, it lists the values of all shell variables.
If options are given, either in short form or using the long
``-/+o longname'' form, it sets or clears the specified options
as described in the section called Argument List Processing.
If you want a command for setting environment variables, that command would be setvar, which you'd use as follows:
setvar dir `pwd`
This is, however, uncommon usage. The more common synonym for this would be:
dir=`pwd`
or
dir=$(pwd)
Note that there are no spaces around the equals sign.
Note also that if you choose to use the setvar command, it's a good idea to put your value inside quotes. The following produces an error:
$ mkdir foo\ bar
$ cd foo\ bar
$ setvar dir `pwd`
Instead, you would need:
$ setvar dir "`pwd`"
Or more clearly:
$ dir="$(pwd)"
Note that you may also need to export your variables. The export command is used to mark a variable that should be passed along to sub shells that the running shell spawns. An example should make this more clear:
$ foo="bar"
$ sh -c 'echo $foo'
$ export foo
$ sh -c 'echo $foo'
bar
One other thing I'll add is that it's common and unnecessary to use date as you're doing in your script, since that command is able to produce its own formatted output. Try this:
$ date '+The date today is %+'
For date options, you can man date and man strftime.
Last tip: when using echo, put things in quotes. You'll produce less confusing and more reasonable output. Note:
$ foo="`printf 'a\nb\n'`"
$ echo $foo
a b
$ echo "$foo"
a
b
Hope this helps!

Bash: in directory `directory_name.git`, how to cd to `../directory_name`?

I'm pretty new to Bash scripting and am looking to do the following:
The script's pwd is "/a/b/c/directory_name.git/" and I'd like to cd to "../directory_name" where directory_name could be anything. Is there any easy way to do this?
I'm guessing I'd have to put the result of pwd in a variable and erase the last 4 characters.
tmpd=${PWD##*/}
cd ../${tmpd%.*}
or perhaps more simply
cd ${PWD%.*}
Test
$ myPWD="/a/b/c/directory_name.git"
$ tmpd=${myPWD##*/}
$ echo "cd ../${tmpd%.*}"
cd ../directory_name
*Note: $PWD does not include a trailing slash so the ${param##word} expansion will work just fine.
Try:
cd `pwd | sed -e s/\.git$//`
The backticks execute the command inside, and use the output of that command as a command line argument to cd.
To debug pipelines like this, it's useful to use echo:
echo `pwd | sed -e s/\.git$//`
This should work:
cd "${PWD%.*}"
Didn't expect to get so many answers so fast so I had time to come up with my own inelegant solution:
#!/bin/bash
PWD=`pwd`
LEN=${#PWD}
END_POSITION=LEN-4
WORKING_COPY=${PWD:0:END_POSITION}
echo $WORKING_COPY
cd $WORKING_COPY
There's probably one above that's much more elegant :)
That's what basename is for:
cd ../$(basename "$(pwd)" .git)

Get current directory or folder name (without the full path)

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

Resources