bash -c does not set $? as expected - linux

Why do the following two commands produce different results?
$ bash -c "ls BLAH; echo $?; export status=$?; echo $status"
ls: cannot access BLAH: No such file or directory
0
^ note the empty line after 0
$ cat test.sh
ls BLAH; echo $?; export status=$?; echo $status
$ bash test.sh
ls: cannot access BLAH: No such file or directory
2
0

The expansions of $? and $status in your first example are done by your current shell - that is before the bash you're running ever sees the command string. Use single quotes:
$ bash -c 'ls BLAH; echo $?; export status=$?; echo $status'
ls: BLAH: No such file or directory
1
0
or otherwise:
$ bash -c "ls BLAH; echo \$?; export status=\$?; echo \$status"
ls: BLAH: No such file or directory
1
0

Related

How to preceed the output generated by "exec &" with a time/Date in linux bash scripts?

I have the following script file that writes files to s3 from a local file system:
#!/bin/bash
CURR_DIR=`dirname $0`
SCRIPT_NAME="$(basename $0)"
LOG_FILE=$(echo $SCRIPT_NAME | cut -f 1 -d '.')
TODAY=$(date '+%Y-%m-%d')
NOW=$(date -d "$(date +%Y-%m-%d)" +%Y"-"%m"-"%d)
LOG_PATH="$CURR_DIR"/logs/"$LOG_FILE"-$TODAY.log
LOG="[$(date '+%Y-%m-%d %H:%M:%S,%3N')] INFO {$LOG_FILE} -"
ERROR_LOG="[$(date '+%Y-%m-%d %H:%M:%S,%3N')] ERROR {$LOG_FILE} -"
BUCKET="s3.bucket.example"
OUT_FOLDER="path/to/folderA"
S3_PUSH="s3://$BUCKET/$OUT_FOLDER"
exec &>> $LOG_PATH
echo "$LOG Copying files to local out folder..." >> $LOG_PATH
cp /path/to/folderA/*.* /path/to/folderB
echo "$LOG Command returned code:" $?
if [ "$(ls -A path/to/folderA/)" ]; then
FILES="$(ls path/to/folderA/*)"
for file in $FILES ; do
echo "$LOG File $file found for sync" >> $LOG_PATH
echo "$LOG Pushing $file to S3 /Folder..." >> $LOG_PATH
echo -n "$LOG " ; s3cmd put -c /home/config/.s3cfg "$file" "$S3_PUSH"/
echo "$LOG Command returned code:" $?
echo "$LOG Copying $file to local backup..." >> $LOG_PATH
mv "$file" /path/to/folderA/backup/
echo "$LOG Command returned code:" $? >> $LOG_PATH
RCC=$?
if [ $? -eq 0 ]
then
echo "$LOG Command returned code:" $?
else
echo "$ERROR_LOG Command returned code:" $?
fi
done
else
echo "$LOG No files found for sync." >> $LOG_PATH
fi
And the output is coming out in a specific grok pattern needed for me to parse this output as logs into Elastic Search, however the line 27 output is as follows:
[2021-09-02 08:15:25,629] INFO {TestGrokScriptPattern} - upload: '/path/to/folderA/File.txt' -> 's3://s3.bucket.example/Path/To/Bucket/File.txt' [1 of 1]
0 of 0 0% in 0s 0.00 B/s done
that upload and 0 of 0 0%... Line is created by the exec & command executed on line 16.
How can I get that output to not go to the next line without the date, time and script name preceeding it in order to not break the log pattern I am trying to create?
Rather than redirect output on each line, you can wrap the body of the script in a single block and then handle the output of the entire block in one place. You can then process that output with the stream editor sed. For example:
if true; then # Always true. Just simplifies redirection.
echo "Doing something..."
command_with_output
command_with_more_output
echo "Done."
fi | sed "s/^/${LOG}/" > ${LOG_PATH} 2>&1
The sed expression means: Substitute (s) the beginning of each line (^) with the contents of the LOG variable.
Using 2>&1 at the end also eliminates the need for the exec &>> $LOG_PATH command.

The existence of more than one word given in parameter, in files content?

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

Set shell script Variable to output of command

Im trying to cd into the md5 hash of whatever variable is set into the script but I do not get the correct value of md5, I think it has something to do with how I'm declaring my variables. Thank you for any help!
#!/bin/bash
var1=$1
md5=$(-n $var1 | md5sum)
cd /var/www/html/$md5
I expected it to take me to a directory given by the md5 hash:
$ ./myscript hello
(no output)
$ pwd
/var/www/html/5d41402abc4b2a76b9719d911017c592
Instead, it gives me errors and tries to cd to the wrong path:
$ ./myscript hello
./myscript: line 3: -n: command not found
./myscript: line 4: cd: /var/www/html/d41d8cd98f00b204e9800998ecf8427e: No such file or directory
$ pwd
/home/me
The md5sum it incorrectly tries to cd to is also the same no matter which value I input.
This works as a solution for anyone else having this issue
#!/bin/bash
md5=$*
hash="$(echo -n "$md5" | md5sum )"
cd /var/www/html/$hash
Your script:
#!/bin/bash
var1=$1
md5=$(-n $var1 | md5sum)
cd /var/www/html/$md5
This has a few issues:
-n is not a valid command in the pipeline -n $var1 | md5sum.
md5sum returns more than just the MD5 digest.
Changing the directory in a script will not be reflected in the calling shell.
Input is used unquoted.
I would write a shell function for this, rather than a script:
function md5cd {
dir="$( printf "%s" "$1" | md5sum - | cut -d ' ' -f 1 )"
cd /var/www/html/"$dir" || return 1
}
The function computes the MD5 digest of the given string using md5sum and cuts off the filename (-) that's part of the output. It then changes directory to the specified location. If the target directory does not exist, it signals this by returning a non-zero exit status.
Extending it to cd to a path constructed from the path on the command line, but with the last path element changed into a MD5 digest (just for fun):
function md5cd {
word="${1##*/}"
if [[ "$word" == "$1" ]]; then
prefix="."
else
prefix="${1%/*}"
fi
dir="$( cut -d ' ' -f 1 <( printf "%s" "$word" | md5sum - ) )"
cd "$prefix"/"$dir" || return 1
}
Testing it:
$ pwd
/home/myself
$ echo -n "hex this" | md5sum
990c0fc93296f9eed6651729c1c726d4 -
$ mkdir /tmp/990c0fc93296f9eed6651729c1c726d4
$ md5cd /tmp/"hex this"
$ pwd
/tmp/990c0fc93296f9eed6651729c1c726d4

I am puzzled with the differences between "sh xxx.sh" and "./xxx.sh" for running a shell script

Here, I have a shell script named load.sh.
It start my program named "demo" with supervise,
When I run it with sh load.sh start | stop, it works well.
However, when I run it with ./load.sh start | stop, it works bad. the demo is frequently started(and exit) by the supervise.
What's the problem of the two ways of running the shell script?
and is there any problem(bug) in the script cause the supervise frequently restart the demo?
Thanks a lot!
#!/bin/bash
cd `dirname $0` || exit
mkdir -p status/demo
dir_name=`pwd`
STR_LIB=${dir_name}/lib
if [ -z "${LD_LIBRARY_PATH}" ]; then
export LD_LIBRARY_PATH="${STR_LIB}"
else
export LD_LIBRARY_PATH="${STR_LIB}:${LD_LIBRARY_PATH}"
fi
start() {
sleep 1
bin/supervise.demo -u status/demo bin/demo >/dev/null 2>&1 &
}
stop() {
if [ -f status/demo/lock ]; then
supervise_demo_pid=`/sbin/fuser status/demo/lock`
`ps -ef | grep "$supervise_demo_pid" | grep "supervise.demo" | grep -v grep > /dev/null 2>&1`
if [ $? -eq 0 ] && [ "$supervise_demo_pid" != "" ] ; then
echo "kill supervise.demo process:"${supervise_demo_pid}
kill -9 $supervise_demo_pid
fi
fi
if [ -f status/demo/status ]; then
demo_pid=`od -An -j16 -N2 -tu2 status/demo/status`
`ps -ef | grep "$demo_pid" | grep "demo" | grep -v grep > /dev/null 2>&1`
if [ $? -eq 0 ]; then
echo "kill demo process:"${demo_pid}
kill -9 $demo_pid
fi
fi
}
case "$1" in
start)
stop
start
echo "Done!"
;;
stop)
stop
echo "Done!"
;;
*)
echo "Usage: $0 {start|stop}"
;;
esac
sh script.sh runs the script in sh, while running it as ./script.sh uses whatever is specified on its first "shebang" line - /bin/bash in this case.
sh and /bin/bash might be different shells, so they interpret the script differently. What sh is depends on your distribution, $PATH, aliases etc.
When you run your script via ./load.sh start | stop it runs with processor that is specified in shebang. In your case it is bash:
#!/bin/bash
What about sh load.sh start | stop. In Ubuntu (by default) sh is actually just a link and in points to dash.
To check it:
$ which sh
/bin/sh
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Mar 16 00:54 /bin/sh -> dash
sh foo will search $path for an executable foo
sh ./foo demands execution from the $cwd
both
foo and ./foo run via the shebang as noted herein
all forms will invoke foo with the perms of the specific file referenced, including suid,guid

Get exit code of command prior to Perl invocation

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

Resources