How to concat pipe result with next command? - bash

I would like to run something like :
pwd | echo "++++ working at : $x"
and the $x variable would show the current directory. I've tried a kind of $(...) stuff with no success.
It has to be in one line to be run in a Dockerfile

BASH gives you an env variable called PWD denoting current working directory hence there is no need to call any external utility just use:
echo "++++ working at : $PWD"

could use xargs
pwd | xargs -I{} echo "++++ working at : {}"
Alternatively you could just not use a pipe and use a subshell
echo "++++ working at : $(pwd)"

backticks to the rescue!
echo "++++ working at : " `pwd`
backticks mean: put the command output right here in the command line as argument(s)

Related

Bash: execute and print command as I'd write it in the terminal

Having executed:
cd ~ && mkdir mytmp && cd mytmp/
echo > somefile
and doing this in a bash script mytmp/myscript.sh:
#!/bin/bash
# version 1
cmd="find . -type f -printf %f\n"
$cmd
renders the wanted result, i.e.:
somefile
myscript.sh
Notice that for some reason I don't need to surround %f\n with quotes as I'd do it if I were to write the command in the terminal. Doing so would render a bad result:
#!/bin/bash
# version 2
cmd="find . -type f -printf '%f\n'"
$cmd
results in:
'somefile
''myscript.sh
'
I need to execute $cmd and at the same time print it as I'd write it in the terminal.
Adding echo $cmd in version 1 executes the command properly but echoes the command without quotes.
Adding echo $cmd in version 2 echoes the command with quotes, like I want, but the result of command execution is bad.
How can I achieve this?
Use set -v.
Example Script
I used some overly complicated script, to test the output for quoting and so on.
#! /bin/bash
set -v
myVariable='test'
# a comment
echo "$(echo "$myVariable") two" | cat -
Output When Running The Script
$ ./myscript
myVariable='test'
# a comment
echo "$(echo "$myVariable") two" | cat -
echo "$myVariable"
test two
As we can see, quotes, variable names, and comments are retained, but commands from subshells will appear twice. Since you don't use any subshells, that shouldn't be a problem.
I actually had the answer to this question, but since I "accidentally" got what I wanted and haven't found the answer online after searching for it, I thought I'd post it here anyway.
The solution is including the quotes as in version 2 and getting rid of them when executing the command by using eval:
#!/bin/bash
cmd="find . -type f -printf '%f\n'"
eval $cmd
echo $cmd

Is there a way to 'inject' a command line output into another command?

for ex :
grep -R "requests" /some/really/long/path/to/type/out
I would like to do something like this
grep -R "requests" (pwd)
Basically, using the output of pwd sorta like a pipe (pipe dosent do it).
Use command substitution:
grep -R "requests" $(pwd)
The output of the command in $(...) is used as an argument list to the command. If you want the output to be treated as one word, wrap it in double quotes:
ls "$( command-that-produces-dirname-containing-whitespace )"
In bash you can use backtics for this:
grep -R "requests" `pwd`
pwd will be executed and the stdout of pwd will be used as the third parameter of the grep command

Get the name of the caller script in bash script

Let's assume I have 3 shell scripts:
script_1.sh
#!/bin/bash
./script_3.sh
script_2.sh
#!/bin/bash
./script_3.sh
the problem is that in script_3.sh I want to know the name of the caller script.
so that I can respond differently to each caller I support
please don't assume I'm asking about $0 cause $0 will echo script_3 every time no matter who is the caller
here is an example input with expected output
./script_1.sh should echo script_1
./script_2.sh should echo script_2
./script_3.sh should echo user_name or root or anything to distinguish between the 3 cases?
Is that possible? and if possible, how can it be done?
this is going to be added to a rm modified script... so when I call rm it do something and when git or any other CLI tool use rm it is not affected by the modification
Based on #user3100381's answer, here's a much simpler command to get the same thing which I believe should be fairly portable:
PARENT_COMMAND=$(ps -o comm= $PPID)
Replace comm= with args= to get the full command line (command + arguments). The = alone is used to suppress the headers.
See: http://pubs.opengroup.org/onlinepubs/009604499/utilities/ps.html
In case you are sourceing instead of calling/executing the script there is no new process forked and thus the solutions with ps won't work reliably.
Use bash built-in caller in that case.
$ cat h.sh
#! /bin/bash
function warn_me() {
echo "$#"
caller
}
$
$ cat g.sh
#!/bin/bash
source h.sh
warn_me "Error: You did not do something"
$
$ . g.sh
Error: You did not do something
g.sh
$
Source
The $PPID variable holds the parent process ID. So you could parse the output from ps to get the command.
#!/bin/bash
PARENT_COMMAND=$(ps $PPID | tail -n 1 | awk "{print \$5}")
Based on #J.L.answer, with more in depth explanations, that works for linux :
cat /proc/$PPID/comm
gives you the name of the command of the parent pid
If you prefer the command with all options, then :
cat /proc/$PPID/cmdline
explanations :
$PPID is defined by the shell, it's the pid of the parent processes
in /proc/, you have some dirs with the pid of each process (linux). Then, if you cat /proc/$PPID/comm, you echo the command name of the PID
Check man proc
Couple of useful files things kept in /proc/$PPID here
/proc/*some_process_id*/exe A symlink to the last executed command under *some_process_id*
/proc/*some_process_id*/cmdline A file containing the last executed command under *some_process_id* and null-byte separated arguments
So a slight simplification.
sed 's/\x0/ /g' "/proc/$PPID/cmdline"
If you have /proc:
$(cat /proc/$PPID/comm)
Declare this:
PARENT_NAME=`ps -ocomm --no-header $PPID`
Thus you'll get a nice variable $PARENT_NAME that holds the parent's name.
You can simply use the command below to avoid calling cut/awk/sed:
ps --no-headers -o command $PPID
If you only want the parent and none of the subsequent processes, you can use:
ps --no-headers -o command $PPID | cut -d' ' -f1
You could pass in a variable to script_3.sh to determine how to respond...
script_1.sh
#!/bin/bash
./script_3.sh script1
script_2.sh
#!/bin/bash
./script_3.sh script2
script_3.sh
#!/bin/bash
if [ $1 == 'script1' ] ; then
echo "we were called from script1!"
elsif [ $1 == 'script2' ] ; then
echo "we were called from script2!"
fi

How can a ksh script determine the full path to itself, when sourced from another?

How can a script determine it's path when it is sourced by ksh? i.e.
$ ksh ". foo.sh"
I've seen very nice ways of doing this in BASH posted on stackoverflow and elsewhere but haven't yet found a ksh method.
Using "$0" doesn't work. This simply refers to "ksh".
Update: I've tried using the "history" command but that isn't aware of the history outside the current script.
$ cat k.ksh
#!/bin/ksh
. j.ksh
$ cat j.ksh
#!/bin/ksh
a=$(history | tail -1)
echo $a
$ ./k.ksh
270 ./k.ksh
I would want it echo "* ./j.ksh".
If it's the AT&T ksh93, this information is stored in the .sh namespace, in the variable .sh.file.
Example
sourced.sh:
(
echo "Sourced: ${.sh.file}"
)
Invocation:
$ ksh -c '. ./sourced.sh'
Result:
Sourced: /var/tmp/sourced.sh
The .sh.file variable is distinct from $0. While $0 can be ksh or /usr/bin/ksh, or the name of the currently running script, .sh.file will always refer to the file for the current scope.
In an interactive shell, this variable won't even exist:
$ echo ${.sh.file:?}
-ksh: .sh.file: parameter not set
I believe the only portable solution is to override the source command:
source() {
sourced=$1
. "$1"
}
And then use source instead of . (the script name will be in $sourced).
The difference of course between sourcing and forking is that sourcing results in the invoked script being executed within the calling process. Henk showed an elegant solution in ksh93, but if, like me, you're stuck with ksh88 then you need an alternative. I'd rather not change the default ksh method of sourcing by using C-shell syntax, and at work it would be against our coding standards, so creating and using a source() function would be unworkable for me. ps, $0 and $_ are unreliable, so here's an alternative:
$ cat b.sh ; cat c.sh ; ./b.sh
#!/bin/ksh
export SCRIPT=c.sh
. $SCRIPT
echo "PPID: $$"
echo "FORKING c.sh"
./c.sh
If we set the invoked script in a variable, and source it using the variable, that variable will be available to the invoked script, since they are in the same process space.
#!/bin/ksh
arguments=$_
pid=$$
echo "PID:$pid"
command=`ps -o args -p $pid | tail -1`
echo "COMMAND (from ps -o args of the PID): $command"
echo "COMMAND (from c.sh's \$_ ) : $arguments"
echo "\$SCRIPT variable: $SCRIPT"
echo dirname: `dirname $0`
echo ; echo
Output is as follows:
PID:21665
COMMAND (from ps -o args of the PID): /bin/ksh ./b.sh
COMMAND (from c.sh's $_ ) : SCRIPT=c.sh
$SCRIPT variable: c.sh
dirname: .
PPID: 21665
FORKING c.sh
PID:21669
COMMAND (from ps -o args of the PID): /bin/ksh ./c.sh
COMMAND (from c.sh's $_ ) : ./c.sh
$SCRIPT variable: c.sh
dirname: .
So when we set the SCRIPT variable in the caller script, the variable is either accessible from the sourced script's operands, or, in the case of a forked process, the variable along with all other environment variables of the parent process are copied for the child process. In either case, the SCRIPT variable can contain your command and arguments, and will be accessible in the case of both sourcing and forking.
You should find it as last command in the history.

Bash modifiers in script

If I run the following in a bash shell:
./script /path/to/file.txt
echo !$:t
it outputs file.txt and all is good.
If in my script I have:
echo $1:t
it outputs /path/to/file.txt:t
How can I get it to output file.txt as per the behaviour I see in a shell? Thanks in advance.
Use the parameter expansion syntax:
echo ${1##*/}
Modifier only work on word designators
In bash you can use the ${1##*/} expansion to get the basename of the file with all leading path components removed:
$ set -- /path/to/file
$ echo "$1"
/path/to/file
$ echo "${1##*/}"
file
You can use this in a script as well:
#!/bin/sh
echo "${1##*/}"
While ${1##*/} will work when Bash is called as /bin/sh, other Bash features require that you use #!/bin/bash at the start of your script. This notation may also not be available in other shells.
A more portable solution is this:
#!/bin/sh
echo `basename "$1"`

Resources