Bash -x or set -x - linux

Is there any practical difference in debugging using bash -x scriptname.sh or using set -x inside the script and then calling it?
In my personal experience I always use bash -x because that way I don't have to change the script so it's less invasive.
Is there a scenario where bash -x would not fit and just set -x would work?
I came up with these questions because I always see people suggesting set -x when trying to help others to debug and I was wondering why not bash -x scriptname.sh. Is there a technical limitation?
PS: I'm not asking about what the commands do, but I'm asking "how" they do and if one is better than the other.
About what they do:
bash -x command
What does `set -x` do?

I would also suggest set -x and not bash -x to a user debugging a script.
The user fully expects to modify the script to fix the bug, so adding a line is a non-issue.
You don't have to know how the user runs their script (it could be via some build script or IDE, and not just ./foo in a terminal)
You don't have to confirm the shebang, since #!/bin/bash -eui would require you to instead run bash -euix file and #!/bin/sh would require sh -x file.
But for myself? Yeah, I'd just run bash -x.

The main reason to avoid things in the shebang is that strictly a POSIX kernel doesn't have to support more than one argument. So #!/usr/bin/env bash -x may not work (#!/bin/bash -x may, but then you may be using the ancient bash on macOS, for example). (That said, many UNIX-like systems do, in fact, support shebangs with multiple arguments.)
If you're dealing with security or secrets options at the first part of your script, but you want to be able to debug later on, you may want to set +x for the security related section. But, you can always do set -x so that is not a particularly robust reason to avoid bash -x.

They are different interface to the same functionality, check out the docs:
All of the single-character options used with the set builtin (see The Set Builtin) can be used as options when the shell is invoked.
The different is. Do I set it before the first line of the script gets evaluated (bash -x ./script.sh), or do I set it within the script (set -x anywhere in the script). They can both enable and disable the simple trace funvtionality, last set "wins" as in affects the lines that follow. There is no functional difference in the effect the two have otherwise.

Related

shell programming difference between #!/bm/bash and #!/bin/sh

Somebody please tell me what's the difference between between #!/bm/bash and #!/bin/sh and links to get better idea please, and why we have to put it at the beggining of a script?
bash and sh are two different shells. Basically bash is sh, with more features and better syntax.
From the bash(1) man page:
If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well.
You have to put on the first line, to signify that the script should always be run with bash, rather than another shell.
One runs the script in a sh shell and the other runs in a bash shell.
See the Difference between sh and bash.

I can't use built-in bash commands when running a script using 'sh' [duplicate]

This question already has answers here:
Why does /bin/sh behave differently to /bin/bash even if one points to the other?
(4 answers)
Closed 6 years ago.
When I execute my script sh myscript.sh I get an error message which states that [[: is an 'unexpected operator', however when i run my script in a bash emulator (http://www.tutorialspoint.com/execute_bash_online.php) it works and doesn't return this error. Furthermore, when i run the script using sh within the emulator it works and doesn't return the error even though on my server it would.
I've checked the link below and, from what i understand, i need to use the bash command. What is wrong with the sh command and how do i enable functions such as [[: to be executed?
NOTE: I am a student and therefore i can only run the bash terminal in school. So any help that will guarantee that this error will not be returned will be hugely appreciated.
[ :Unexpected operator in shell programming
Simple answer is to just use bash myscript.sh. As has been mentioned below, the [[ syntax is bash specific, and not supported by sh.
These are two separate shells, each with their own variation on the scripting language. A vulgar analogy would be that bash is to sh, what c++ is to c. Bash has more features, and some easier syntax, but they share a lot in common.
If you have #!/bin/bash at the top of your file, then it's a bash script. You run this by entering bash yourscript.sh if it is not executable, or simply ./yourscript.sh if it is.
If you have #!/bin/sh, then it's an sh script. You run this by the same principles described above.
You could think about it like this:
There are many "human languages" (French, Japanese, English, Hindi etc)
There are many different "shell languages" (sh, csh, tcsh, zsh, bash etc)
Think of sh and bash as languages, not commands.
The errors you are getting is because your computer is expecting you to talk to it in sh, but actually you are talking to it in bash. It is like giving a French document to a German translator....
So, to resolve this, you just need to inform your computer that your script is written in bash.
To do this, simply add this line to the very top of your script file:
#!/bin/bash
Many Linux distributions use a smaller, simpler shell implementation than Bash for their default sh binary. They do this for various reasons. If you need Bash, run bash explicitly.
[[ is a Bash keyword similar to (but more powerful than) the [ command.
See
Bash FAQ 31
Test and Conditionals.
Unless you're writing for POSIX sh, it is recommended to use [[ instead of [.

Checking whether a program exists

In the middle of my perl script I want to execute a bash command. The script takes a long time, so at the beginning of the script I want to see if the command exists. This answer says to just try and run it and this other answer suggests some bash commands to test if the program exists.
Is the latter option the best solution? Are there any better ways to do this check in perl?
My best guess is that you want to check for existence of an executable file that you want to run using system or qx//
But if you want your command line to behave the same way as the shell, then you can probably use File::Which
What if we assume that we don't know the command's location?
This means that syck's answer won't work, and zdim's answer is incomplete.
Try this function in perl:
sub check_exists_command {
my $check = `sh -c 'command -v $_[0]'`;
return $check;
}
# two examples
check_exists_command 'pgrep' or die "$0 requires pgrep";
check_exists_command 'readlink' or die "$0 requires readlink";
I just tested it, because I just wrote it.
With perl, you can test files for existence, readability, executability etc., take a look here.
Therefore just use
executeBashStuff() if -x $filename;
or stat it:
stat($filename);
executeBashStuff() if -x _;
To me a better check is to run the program at the beginning of the script (with -V say).
I'd use the same invocation as you use to run the job later (via shell or not, via execvp). Once at it, make sure to see whether it threw errors. This is also discussed in your link but I would in fact get the output back (not send it away) and check that. This is the surest way to see whether the thing actually runs out of your program and whether it is what you expect it to be.
Checking for the executable with -x (if you know the path) is useful, too, but it only tells you that a file with a given name is there and that it is executable.
The system's which seems to be beset with critism for its possible (mis)behavior, it may or may not be a shell-builtin (which complicates how exactly to use it), is an external utility, and its exact behavior is system dependent. The module File::Which pointed out in Borodin's answer would be better -- if it is indeed better than which. (What it may well be, I just don't know.)
Note. I am not sure what "bash command" means: a bash shell built-in, or the fact that you use bash when on terminal? Perl's qx and system use the sh shell, not bash (if they invoke the shell, which depends on how you use them). While sh is mostly a link, and often to bash, it may not be and there are differences, and you cannot rely on your shell configuration.
Can also actually run a shell, qx(/path/bash -c 'cmd args'), if you must. Mind the quotes. You may need to play with it to find the exact syntax on your system. See this page and links.

Dry-run a potentially dangerous script?

A predecessor of mine installed a crappy piece of software on an old machine (running Linux) which I've inherited. Said crappy piece of software installed flotsam all over the place, and also is sufficiently bloated that I want it off ASAP -- it no longer has any functional purpose since we've moved on to better software.
Vendor provided an uninstall script. Not trusting the crappy piece of software, I opened the uninstall script in an editor (a 200+ line Bash monster), and it starts off something like this:
SWROOT=`cat /etc/vendor/path.conf`
...
rm -rf $SWROOT/bin
...
It turns out that /etc/vendor/path.conf is missing. Don't know why, don't know how, but it is. If I had run this lovely little script, it would have deleted the /bin folder, which would have had rather amusing implications. Of course this script required root to run!
I've dealt with this issue by just manually running all the install commands (guh) where sensible. This kind of sucked because I had to interpolate all the commands manually. In general, is there some sort of way I can "dry run" a script to have it dump out all the commands it would execute, without it actually executing them?
bash does not offer dry-run functionality (and neither do ksh, zsh, or any other shell I know).
It seems to me that offering such a feature in a shell would be next to impossible: state changes would have to be simulated and any command invoked - whether built in or external - would have to be aware of these simulations.
The closest thing that bash, ksh, and zsh offer is the ability to syntax-check a script without executing it, via option -n:
bash -n someScript # syntax-check a script, without executing it.
If there are no syntax errors, there will be no output, and the exit code will be 0.
If there are syntax errors, analysis will stop at the first error, an error message including the line number is written to stderr, and the exit code will be:
2 in bash
3 in ksh
1 in zsh
Separately, bash, ksh, and zsh offer debugging options:
-v to print each raw source code line[1]
to stderr before it is executed.
-x to print each expanded simple command to stderr before it is executed (env. var. PS4 allows tweaking the output format).
Combining -n with -v and/or -x offers little benefit:
With -n specified, -x has no effect at all, because nothing is being executed.
With -n specified, -v will effectively simply print the source code.
If there is a syntax error, there may be benefit in the source code getting print up to the point where the error occurs; keep in mind, though that the error message produced by
-n always includes the offending line number.
[1] Typically, it is individual lines that are printed, but the true unit is however many lines a given command - which may be a compound command such as while or a command list (such as a pipeline) - spans.
You could try running the script under Kornshell. When you execute a script with ksh -D, it reads the commands and checks them for syntax, but doesn't execute them. Combine that with set -xv, and you'll print out the commands that will be executed.
You can also use set -n for the same effect. Kornshell and BASH are fairly compatible with each other. If it's a pure Bourne shell script, both Kornshell and BASH will execute it pretty much the same.
You can also run ksh -u which will cause unset shell variables to cause the script to fail. However, that wouldn't have caught the catless cat of a nonexistent file. In that case, the shell variable was set. It was set to null.
Of course, you could run the script under a restricted shell too, but that's probably not going to uninstall the package.
That's the best you can probably do.

How can I debug the bash prompt?

I've been editing .bashrc files and other init files, and it seems that I've left behind a few code snippets or two that are causing a few errors at the prompt (e.g. file missing), but I can't find them.
How do I debug the prompt to find out what init scripts I've carelessly hacked?
Most of the shells have debug flags that show the commands being executed. Bash may even have one that shows a command before expansion of variables and after. Have you tried checking (I believe) -c -x or -X flags and see if they show the information you are looking for.
You can set them as first thing in the rc files (most global one) or just pass it down into bash command by invoking it from another shell.
In fact, if you invoke bash from another shell, you can also use script command to record everything you see and do into the file, which makes postmortem analysis so much easier.
Try invoking bash with the -x flag, then sourcing your .bashrc or .bash_profile or whatever you're using. That ought to be prolix enough to find your problem
ie:
bash -x
source .bashrc
The easiest way to get a clean initial state is to SSH into your current host, but instead of letting SSH launch your shell with default settings, you provide an explicit command which prevents .bashrc from being read.
ssh -tt localhost /bin/bash --norc
The -tt forces SSH to allocate a TTY, which is what would normally happen when you open a shell connection, but is not default when running an explicit command.
The --norc prevents bash from reading your settings file (since we want to do that ourselves).
You should now be at a bash prompt, in a clean environment. This is useful for examining what variable are set to before your .bashrc runs etc. Enable tracing and source your .bashrc:
set -x # Enable tracing
source .bashrc
Try to see where you've defined prompt - probably it in some dot file in your home directory:
grep PS1 ~/.*
You can see current value of prompt by just printing it:
echo $PS1
HTH
Check the .bash_history file in your home directory to find out what commands you have been running. If you used commands like vi filename to open the init scripts, it will find them in the command history.

Resources