Better way to make a bash script self-tracing? - bash

I have certain critical bash scripts that are invoked by code I don't control, and where I can't see their console output. I want a complete trace of what these scripts did for later analysis. To do this I want to make each script self-tracing. Here is what I am currently doing:
#!/bin/bash
# if last arg is not '_worker_', relaunch with stdout and stderr
# redirected to my log file...
if [[ "$BASH_ARGV" != "_worker_" ]]; then
$0 "$#" _worker_ >>/some_log_file 2>&1 # add tee if console output wanted
exit $?
fi
# rest of script follows...
Is there a better, cleaner way to do this?

#!/bin/bash
exec >>log_file 2>&1
echo Hello world
date
exec has a magic behavior regarding redirections: “If command is not specified, any redirections take effect in the current shell, and the return status is 0. If there is a redirection error, the return status is 1.”
Also, regarding your original solution, exec "$0" is better than "$0"; exit $?, because the former doesn't leave an extra shell process around until the subprocess exits.

maybe you are looking for set -x?

you may check a common open source trace library with support for bash.
http://sourceforge.net/projects/utalm/
http://www.unifiedsessionsmanager.org/en/downloads.html
The current available component is for scripting by bash, soon available are Python and C++. Additional going to follow are: Ruby, Java, JavaScript, SQL, PowerShell,...
The license is Apache-2.0
WKR
Arno-Can Uestuensoez

Related

Use a variable on a script command line when its value isn't set until after the script starts

How to correctly pass to the script and substitute a variable that is already defined there?
My script test.sh:
#!/bin/bash
TARGETARCH=amd64
echo $1
When I enter:
bash test.sh https://example/$TARGETARCH
I want to see
https://example/amd64
but I actually see
https://example/
What am I doing wrong?
The first problem with the original approach is that the $TARGETARCH is removed by your calling shell before your script is ever invoked. To prevent that, you need to use quotes:
./yourscript 'https://example.com/$TARGETARCH'
The second problem is that parameter expansions only happen in code, not in data. This is, from a security perspective, a Very Good Thing -- if data were silently treated as code it would be impossible to write secure scripts handling untrusted data -- but it does mean you need to do some more work. The easy thing, in this case, is to export your variable and use GNU envsubst, as long as your operating system provides it:
#!/bin/bash
export TARGETARCH=amd64
substitutedValue=$(envsubst <<<"$1")
echo "Original value was: $1"
echo "Substituted value is: $substitutedValue"
See the above running in an online sandbox at https://replit.com/#CharlesDuffy2/EcstaticAfraidComputeranimation#replit.nix
Note the use of yourscript instead of test.sh here -- using .sh file extensions, especially for bash scripts as opposed to sh scripts, is an antipattern; the essay at https://www.talisman.org/~erlkonig/documents/commandname-extensions-considered-harmful/ has been linked by the #bash IRC channel on this topic for over a decade.
For similar reasons, changing bash yourscript to ./yourscript lets the #!/usr/bin/env bash line select an interpreter, so you aren't repeating the "bash" name in multiple places, leading to the risk of those places getting out of sync with each other.

How can I invoke both BASH and CSH shells in a same script

In the same script, I want to use some CSH commands and some BASH commands.
Invoking one after the other giving me problems despite I am following the different syntax for respective shells. I want to know where is mistake in my code
Your suggestions are appreciated!!
I am a beginner to shell, especially so for CSH. but the code I got has been written in CSH entirely. Since I have some familiarity with CSH, I wanted to tweak the existing CSH code by including BASH commands, which I am comfortable using it. When I tried BASH commands after CSH by invoking !#/bin/bash, it is giving some errors. I want to know if I am missing any options!!
#!/bin/csh
----
----
----
#!/bin/bash
dir2in="/nethome/achandra/NCEI/CCSM4_Historical/Forecasts"
filin2 ="ccsm4_0_cfsrr_Fcst.${ENS}.cam2.h1.${yyear[${iimonth}]}-${mmon[${iimonth}]}-${ssday}-00000.nc"
cp $dirin/$filin /nethome/achandra/NCEI/CCSM4_Historical_Forecasts/
ln -s /nethome/achandra/NCEI/CCSM4_Historical/Forecasts/$filin /nethome/achandra/NCEI/CCSM4_Historical_Forecasts/"${$filin%.nc.cdo}.nc"
#!/bin/csh
I am getting errors such as
"dirin: Undefined variable."
You are asking here for "embedding one language into another", which, as #Bayou already explained, is not supported directly. Maybe you were spoiled from the HTML-world, where you can squeeze CSS and Javascript in between and maybe use some server side PHP or Ruby stuff too.
The closest to this are HERE-documents. If you write inside your bash script a
csh <<CSH_END
your ...
csh ....
commands ...
go here ...
CSH_END
these commands are executed in a child process driven by csh. It works the other way around with bash in the same way. Make sure that the terminator symbol (CSH_END in my example) starts in column 1.
Whether this will work for your application, I can't say, because things which run in the same process in your original script, now run in different processes.
You can't mix them up like you're suggesting. It's like asking "can I use PHP code in a Python script". However, most of the shells have options to run commands (-c), just as csh does. For using Bash within a sh script:
#! /bin/sh
CONDITION=$(/bin/bash -c "[[ 1 > 2 ]] || echo no")
echo $CONDITION
exit 0
Otherwise you could create separate files and execute them.
#! /bin/sh
CONDITION=$(./bash-script.sh)
echo $CONDITION
exit 0
You, of course, should use csh instead of sh. Both of my scripts will output the following text.
$ ./test.sh
no

How to check for bash shell syntax errors in practice?

I need to check whether a set of scripts are syntactically correct. I know there are couple of posts around suggesting to use bash -n <script_name> but I run this and I get nothing out e.g.
good.sh
#!/bin/bash
echo "hello"
bad.sh
#!/bin1/bash1
eco "hello"
If I do:
$ bash -n bad.sh; echo $?
0
$ bash -n good.sh; echo $?
0
So how do you discriminate between good and bad exactly?
bash -n checks whether Bash can parse the code (that is, the syntax is correct), not whether the code is "correct." "Correct" can have a lot of meanings, most of which programs will never be able to verify:
Can be parsed (bash -n).
Finishes without error (if ./script.sh; then [...]; fi).
Prints something which follows a specific format.
Prints something useful.
Any of the above within a specific environment, for example one which has a shell interpreter that lives in /bin1/bash1 and a command eco which shows you the most ecologically friendly beer bottles available within a 5 parsec radius.

Bash Unix Shell Script to Accept Input and Set System Variables

I'm trying to modify an existing shell script to accept user input and handle some system exports. The below is an excerpt from a larger script. After running this script, I echo $TEST_DIR and I don't get anything back. Any ideas?
#!/bin/sh
if [ -z "$TEST_DIR" ]
then
echo "TEST_DIR was not set, please enter the path: "
read input_variable
export TEST_DIR=$input_variable
exit 1
fi
Save this as script.sh.
#!/bin/bash
if [ -z "$TEST_DIR" ]
then
echo "TEST_DIR was not set, please enter the path: "
read input_variable
export TEST_DIR=$input_variable
fi
And run it like this:
. ./script.sh
Or, equivalently:
source ./script.sh
source runs the script in the current environment and consequently lets you modify the environment in the script. Without it every command, which runs as a child of the shell process, is only given a copy of the current environment.
Note I removed the exit line, as it would terminate the shell in this case.
The problem is that you're running the script in a subshell - it's setting your TEST_DIR properly, but then the shell exits, and the parent shell doesn't keep the change.
You might be better off making this a function you can source from the parent shell. Shell scripts can't modify the environment of the calling shell, but a function can modify the shell it's executing in.
You probably don't want to do exit 1; that is used to indicate failure. You'd use exit 0 to indicate success — but, as described below, you don't want to do that, either, in this case.
The other problem is that if you run the script as child process, it cannot affect the environment of the parent process.
To work around that, in POSIX shells, you need to use:
. your_script.sh
or, in bash, you can also use:
source your_script.sh
The script does not have to be executable, just readable, and will be searched for on $PATH unless you specify a name containing a slash, just like other commands.
And, when you do the . or source, you definitely do not want any exit in the script because that will cause the main shell to exit. The shell reads the file as if you were typing it at standard input (more or less), so an exit that's executed will exit the shell.

How to make shell scripts robust to source being changed as they run

Have people noticed that if you modify the source of a shell script, any instances that are currently running are liable to fail?
This in my opinion is very bad; it means that I have to make sure all instances of a script are stopped before I make changes. My preferred behavior would be that existing scripts continue running with old source code and that new instances use the new code (e.g. what happens for perl and python programs).
Do folks have any good workarounds for this behavior, other than pre-copying the shell script to a tempfile and running from that?
Thanks,
/YGA
Very slight addition to the other answers:
#!/bin/sh
{
# Your stuff goes here
exit
}
The exit at the end is important. Otherwise, the script file might still be accessed at the end to see if there are any more lines to interpret.
This question was later reposted here: Can a shell script indicate that its lines be loaded into memory initially?
Make sure the shell has to parse the whole file before executing any of it:
#!/bin/ksh
{
all the original script here
}
That does the trick.
Incidentally, with Perl (and I assume Python), the program parses the entire file before executing any of it, exactly as recommended here. Which is why you don't usually run into the problem with Perl or Python.
The desired behavior may not be possible, depending on complexity of the shell scripts that are involved.
If the full shell script is contained in a single source file, and that file is fully parsed before execution, then the shell script is generally safe from modifications to the copy on the disc during execution. Wrapping all the executable statements into a function (or series of functions) will generally achieve the goal you are after.
#!/bin/sh
doit()
{
# Stuff goes here
}
# Main
doit
The difficulty comes when the shell script "includes" other shell scripts (e.g. ".", or "source"). If these includes are wrapped in a function, they are not parsed until that statement is reached in the flow of execution. This makes the shell script vulnerable to changes to that external code.
In addition, if the shell script runs any external program (e.g. shell script, compiled program, etc), that result is not captured until that point in the execution is reached (if ever).
#!/bin/sh
doit()
{
if [[some_condition]] ; then
resultone=$(external_program)
fi
}
# Main
doit
this answer contains a robust and self contained way to make a script resistant to this problem: have the script copy and re-execute itself like this:
#!/bin/bash
if [[ $0 != /tmp/copy-* ]] ; then
rm -f /tmp/copy-$$
cp $0 /tmp/copy-$$
exec /tmp/copy-$$ "$#"
echo "error copying and execing script"
exit 1
fi
rm $0
# rest of script...
(This will not work if the original script begins with the characters /tmp/copy-)

Resources