How do I inside Perl get the exit code of the command run prior to the Perl invocation?
$ ls asdf
ls: asdf: No such file or directory
$ perl -le 'print $?'
0
I want it to return 2 (exit status of ls).
$ ls asdf
ls: asdf: No such file or directory
$ perl -le 'print $ENV{"?"}'
Returns blank line.
$ ls asdf
ls: asdf: No such file or directory
$ perl -le "print $?"
2
Using shell interpolation I can get my result. But this is not what I want since I need the exit code in a stand alone Perl script.
Try doing this :
xxx; perl -le 'print $ARGV[0]' $?
or
xxx; perl -le 'print '"$?"''
but the latter one depends too much of the SHELL you are using and should be avoided
Try:
$ ls asdf
$ EC=$? perl -le 'print $ENV{"EC"}'
perl -le 'print $?' doesn't work because there is no relation between $? in your shell and the one in Perl - Perl just re-used the name for simplicity.
$? is not a true environment variable.
$ export '?'
-bash: export: `?': not a valid identifier
That means you'll need to explicitly pass it to the script. The two safe and easy ways are
perl -E'say $ARGV[0]' $?
and
EC=$? perl -E'say $ENV{EC}'
But I understand you'd rather not have to specify $?. If that's so, then what you should do is have the previous command executed by your script instead of having it executed before your script
#!/usr/bin/perl
# usage: wrapper program [arg [...]]
use feature qw( say );
system { $ARGV[0] } #ARGV;
say $? & 0x7F ? 0x80 | ($? & 0x7F) : $? >> 8;
$ true
$ echo $?
0
$ wrapper true
0
$ false
$ echo $?
1
$ wrapper false
1
$ perl -e'kill INT => $$'
$ echo $?
130
$ wrapper perl -e'kill INT => $$'
130
My script is a sms utility which you can call after running some command taking hours/days execution time, e.g. big_job; sms.pl +4512131415 "job is done". I wanted the message to be prefixed with 'ok' or 'error' depending on the exit status.
# usage: notify bigjob [arg [...]]
phone="+4512131415"
if "$#" ; then
sms.pl "$phone" "job succeeded"
else
sms.pl "$phone" "job failed"
fi
Related
I want to check if a file contains a specific string or not in bash. I used this script, but it doesn't work:
if [[ 'grep 'SomeString' $File' ]];then
# Some Actions
fi
What's wrong in my code?
if grep -q SomeString "$File"; then
Some Actions # SomeString was found
fi
You don't need [[ ]] here. Just run the command directly. Add -q option when you don't need the string displayed when it was found.
The grep command returns 0 or 1 in the exit code depending on
the result of search. 0 if something was found; 1 otherwise.
$ echo hello | grep hi ; echo $?
1
$ echo hello | grep he ; echo $?
hello
0
$ echo hello | grep -q he ; echo $?
0
You can specify commands as an condition of if. If the command returns 0 in its exitcode that means that the condition is true; otherwise false.
$ if /bin/true; then echo that is true; fi
that is true
$ if /bin/false; then echo that is true; fi
$
As you can see you run here the programs directly. No additional [] or [[]].
In case if you want to check whether file does not contain a specific string, you can do it as follows.
if ! grep -q SomeString "$File"; then
Some Actions # SomeString was not found
fi
In addition to other answers, which told you how to do what you wanted, I try to explain what was wrong (which is what you wanted.
In Bash, if is to be followed with a command. If the exit code of this command is equal to 0, then the then part is executed, else the else part if any is executed.
You can do that with any command as explained in other answers: if /bin/true; then ...; fi
[[ is an internal bash command dedicated to some tests, like file existence, variable comparisons. Similarly [ is an external command (it is located typically in /usr/bin/[) that performs roughly the same tests but needs ] as a final argument, which is why ] must be padded with a space on the left, which is not the case with ]].
Here you needn't [[ nor [.
Another thing is the way you quote things. In bash, there is only one case where pairs of quotes do nest, it is "$(command "argument")". But in 'grep 'SomeString' $File' you have only one word, because 'grep ' is a quoted unit, which is concatenated with SomeString and then again concatenated with ' $File'. The variable $File is not even replaced with its value because of the use of single quotes. The proper way to do that is grep 'SomeString' "$File".
Shortest (correct) version:
grep -q "something" file; [ $? -eq 0 ] && echo "yes" || echo "no"
can be also written as
grep -q "something" file; test $? -eq 0 && echo "yes" || echo "no"
but you dont need to explicitly test it in this case, so the same with:
grep -q "something" file && echo "yes" || echo "no"
##To check for a particular string in a file
cd PATH_TO_YOUR_DIRECTORY #Changing directory to your working directory
File=YOUR_FILENAME
if grep -q STRING_YOU_ARE_CHECKING_FOR "$File"; ##note the space after the string you are searching for
then
echo "Hooray!!It's available"
else
echo "Oops!!Not available"
fi
grep -q [PATTERN] [FILE] && echo $?
The exit status is 0 (true) if the pattern was found; otherwise blankstring.
if grep -q [string] [filename]
then
[whatever action]
fi
Example
if grep -q 'my cat is in a tree' /tmp/cat.txt
then
mkdir cat
fi
In case you want to checkif the string matches the whole line and if it is a fixed string, You can do it this way
grep -Fxq [String] [filePath]
example
searchString="Hello World"
file="./test.log"
if grep -Fxq "$searchString" $file
then
echo "String found in $file"
else
echo "String not found in $file"
fi
From the man file:
-F, --fixed-strings
Interpret PATTERN as a list of fixed strings, separated by newlines, any of
which is to be matched.
(-F is specified by POSIX.)
-x, --line-regexp
Select only those matches that exactly match the whole line. (-x is specified by
POSIX.)
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero
status if any match is
found, even if an error was detected. Also see the -s or --no-messages
option. (-q is specified by
POSIX.)
Try this:
if [[ $(grep "SomeString" $File) ]] ; then
echo "Found"
else
echo "Not Found"
fi
I done this, seems to work fine
if grep $SearchTerm $FileToSearch; then
echo "$SearchTerm found OK"
else
echo "$SearchTerm not found"
fi
grep -q "something" file
[[ !? -eq 0 ]] && echo "yes" || echo "no"
I have 2 scripts . I'm invoking one script from the other for capturing the exit status.
Import.sh
SCHEMA=$1
DBNAME=$2
LOGPATH=/app/dbimport/PreImport_`date +%d%b%Y`.log
export ORACLE_HOME=/oracle/product/11.2.0/db
set -x
for line in `cat "$SCHEMA" | egrep -w 'PANV|PANVPXE'`
do
USER=`echo "$line" |cut -d ';' -f1`
echo "Fetching User : $USER" >> "$LOGPATH"
PASSWORD=`echo "$line" | cut -d ';' -f2`
echo "Fetching Password: $PASSWORD" >> "$LOGPATH"
SOURCE=`echo "$line" | cut -d ';' -f3`
echo "Fetching Source Schema : $SOURCE" >> "$LOGPATH"
done
exit $?
temp.sh
RC=`/app/arjun/scripts/Import.sh schema_remap_AANV02_UAT2.txt ARJSCHEMA`
echo "Return code = $RC"
schema_remap_AANV02_UAT2.txt
AANVPXE;Arju4578;PANVPXE
AANVSL;Arj0098;PANVSL
AANV;Arju1345;PANV
the .txt file does not have read permission(make sure that you do not give read permission), so the script should fail by returning the exit status as exit $? .
Below is the output after i run temp.sh
+ cat schema_remap_AANV02_UAT2.txt
+ egrep -w 'PANV|PANVPXE'
cat: schema_remap_AANV02_UAT2.txt: Permission denied
+ exit 1
Return code =
Internal scripts is exiting with exit 1(since cat command is failing) , but inside temp.sh i'm not getting the expected value while capturing the return code.
I want to make sure that whichever command fails in import.sh , the script should return with appropriate exit status.
To get the exit code of your script Import.sh instead of its output, change the script temp.sh to
/app/arjun/scripts/Import.sh schema_remap_AANV02_UAT2.txt ARJSCHEMA
RC=$?
echo "Return code = $RC"
or simply
/app/arjun/scripts/Import.sh schema_remap_AANV02_UAT2.txt ARJSCHEMA
echo "Return code = $?"
See the comments for hints how to fix/improve your scripts.
I tried to understand the way you invoke your child script ( Import ) into the parent script ( temp.sh ). Well let me show you what is happening
Import Script
SCHEMA=$1
DBNAME=$2
LOGPATH=/app/dbimport/PreImport_`date +%d%b%Y`.log
export ORACLE_HOME=/oracle/product/11.2.0/db
set -x
for line in `cat "$SCHEMA" | egrep -w 'PANV|PANVPXE'`
do
USER=`echo "$line" |cut -d ';' -f1`
echo "Fetching User : $USER" >> "$LOGPATH"
PASSWORD=`echo "$line" | cut -d ';' -f2`
echo "Fetching Password: $PASSWORD" >> "$LOGPATH"
SOURCE=`echo "$line" | cut -d ';' -f3`
echo "Fetching Source Schema : $SOURCE" >> "$LOGPATH"
done
exit $?
This script will exit with something different than 0 when a problem with the grep occurs, so if the pattern you are looking for it is not there, it will fail.
$ echo "hello" | egrep -i "bye"
$ echo $?
1
Then you are running this script from other program
Launcher
RC=`/app/arjun/scripts/Import.sh schema_remap_AANV02_UAT2.txt ARJSCHEMA`
echo "Return code = $RC"
Here is where you have the problem. You are calling the script like it was a function and expecting a result. The variable RC is getting whatever output your script is sending to the STDOUT , nothing else. RC will be always empty, because your script does not send anything to the STDOUT. So, you must understand the difference between getting the result from the child and evaluating what return code produced the child program.
Let me show you an example of what I just explained to you using my own scripts. I have two scripts: the child.sh is just a sqlplus to Oracle. the parent invokes the child the same way you do.
$ more child.sh
#/bin/bash
$ORACLE_HOME/bin/sqlplus -S "/ as sysdba" << eof
whenever sqlerror exit failure;
select * from dual ;
eof
if [[ $? -eq 0 ]];
then
exit 0;
else
exit 99;
fi
$ more parent.sh
#!/bin/bash
# run child
var=`/orabatch/ftpcpl/log/child.sh`
echo $var
$ ./child.sh
D
-
X
$ echo $?
0
$ ./parent.sh
D - X
$ echo $?
0
As you see, my parent is getting whatever the child script is sending to the STDOUT. Now let's force an error in the child script to verify that my parent script is still exiting as ok:
$ ./child.sh
select * from dual0
*
ERROR at line 1:
ORA-00942: table or view does not exist
$ ./parent.sh
ERROR at line 1:
ORA-00942: table or view does not exist
$ echo $?
0
As you can see, the output of my operation is the error in the first, however not as an error, but as an output. my parent has ended ok, even you can see that there was an error.
I would rewrite the script as follows:
#/bin/bash
my_path=/app/arjun/scripts
$my_path/Import.sh schema_remap_AANV02_UAT2.txt ARJSCHEMA
result=$?
if [ ${result} -ne 0 ];
then
echo "error"
exit 2;
fi
Here is my code:
#!/bin/bash
if [[ $1 = "" ]]; then
exit 0
fi
array=($(cat $1))
let b=${#array[#]}-1
count=0
for i in {1..7}; do
for j in {30..37}; do
for n in {40..47}; do
if [[ $count -gt $b ]]; then
printf '\n'
printf '\e[0m'
exit 1
fi
printf '\e[%s;%s;%sm%-5s' "$i" "$j" "$n" "${array[$count]}"
printf '\e[0m'
let count=$count+1
done
printf '\n'
done
done
#printf '\n'
printf '\e[0m'
exit 0
The problem is that when I start it like this
. color.sh arg
or without argument, it just closes. I know that the reason for that is exit. Is there any way correct my code so I could start a script with dot at start and terminal wouldn't close after execution? I don't want to start it like this: ./script
Replace all exit with return.
return inside a sourced script will even work with exit codes:
$ . <(echo "echo before; return 0; echo after")
before
$ echo $?
0
$ . <(echo "echo before; return 7; echo after")
before
$ echo $?
7
When you use the dot to run a script you are "sourcing" it, which means the interpreter reads and executes all the commands in that script in the context of the current environment without spawning a subshell, as if you had typed each yourself.
That's why if you source it you can set variables in a script that will remain after it has run, whereas running it in a subshell would encapsulate them, and they would go away when the script ends.
Accordingly, if you source a script that hits an exit, it causes the calling environment to exit. Use return as Socowi suggested.
With bash 4.1.2 and 4.3.48, the following script gives the expected output:
#!/bin/bash
returnSimple() {
local __resultvar=$1
printf -v "$__resultvar" '%s' "ERROR"
echo "Hello World"
}
returnSimple theResult
echo ${theResult}
echo Done.
Output as expected:
$ ./returnSimple
Hello World
ERROR
Done.
However, when stdout from the function is piped to another process, the assignment of the __resultvar variable does not work anymore:
#!/bin/bash
returnSimple() {
local __resultvar=$1
printf -v "$__resultvar" '%s' "ERROR"
echo "Hello World"
}
returnSimple theResult | cat
echo ${theResult}
echo Done.
Unexpected Output:
$ ./returnSimple
Hello World
Done.
Why does printf -v not work in the second case? Should printf -v not write the value into the result variable independent of whether the output of the function is piped to another process?
See man bash, section on Pipelines:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
That's why when you write cmd | cat, cmd receives a copy of variable that it can't modify.
A simple demo:
$ test() ((a++))
$ echo $a
$ test
$ echo $a
1
$ test | cat
$ echo $a
1
Interestingly enough, the same also happens when using eval $__resultvar="'ERROR'" instead of the printf -v statement. Thus, this is not a printf related issue.
Instead, adding a echo $BASH_SUBSHELL to both the main script and the function shows that the shell spawns a sub shell in the second case - since it needs to pipe the output from the function to another process. Hence the function runs in a sub shell:
#!/bin/bash
returnSimple() {
local __resultvar=$1
echo "Sub shell level: $BASH_SUBSHELL"
printf -v "$__resultvar" '%s' "ERROR"
}
echo "Sub shell level: $BASH_SUBSHELL"
returnSimple theResult | cat
echo ${theResult}
echo Done.
Output:
% ./returnSimple.sh
Sub shell level: 0
Sub shell level: 1
Done.
This is the reason why any variable assignments from within the function are not passed back to the calling script.
I'm trying to find a command on bash shell, which allows me to verify if all words given in parameter(in the list $*), exist in the current directory I'm in.
Exemple, if I execute this command:
bash ./exp_quotes.sh hadir Trex blabla
How to test the existence of the tree words in one command, and get a value of 1 or 0 as $? ?
If you want to check if all patterns exist in file,
you can write exp_quotes.sh like this:
#!/usr/local/env bash
for arg; do
grep -q "$arg" file || exit 1
done
This script will exit with 1 (failure) if any of the arguments is not in file.
Otherwise it will exit with 0 (success).
You can make grep do all the job of searching for all patterns.
You just need to concatenate all of them together with |, as this:
$ echo "$(IFS=\|; echo "$*")"
hadir|Trex|blabla
Then, you just need to use grep to do the search:
$ cat ./exp_quotes.sh
#!/bin/bash
file="$1"
grep -qE "\"$(IFS=\|; echo "$*")\"" "$file" && exit 1 || exit 0
Change the permissions (to run) of the script:
$ chmod u+x ./exp_quotes.sh
And execute it with the filename first and the patterns:
$ ./exp_quotes.sh file hadir Trex blabla
This may be used even for patterns with spaces:
$ ./exp_quotes.sh "file name with spaces" "a hadir with spaces" Trex blabla
If what you need is to list the files that do contain all the words, use this:
$ cat ./exp_quotes.sh
#!/bin/bash
grep -lE "\"$(IFS=\|; echo "$*")\"" *
To have an exit code of 0 if "at least one word is found" is the same as "exit 0 if one word OR other is found"
That would be:
#!/bin/bash
infile="$1"
grep -qE "\"$(IFS=\|; echo "$*")\"" infile && exit 0 || exit 1