how to skip first argument when running bash script? - bash

Say I have a bash script with two optional arguments
How would I be able to run the script providing an input for the second argument, but not the first argument?

The shell's argument list is just a sequence of strings. There is no way for the first string to be undefined and the second to be defined, but if you have control over the program, or the person who wrote it anticipated this scenario, perhaps it supports passing in an empty first argument, or perhaps a specific string which is interpreted as "undefined".
To pass in an empty string, the shell allows you to put two adjacent quotes (which will be removed by the shell before the argument is passed on to the program you are running, by way of how quotes are handled by the shell in general).
program '' second third fourth
A common related convention is to let a lone or double dash signify "an option which isn't an option".
program -- second third fourth
If you have control over the command and its argument handling (and it's not already cemented because you have programs written by other people which depend on the current behavior) a better design would be to make the optional argument truly optional, i.e. maybe make the first argument a dash option.
program --uppercase string of arguments
program --lowercase STRING OF SHOUTING
program The arguments will be passed through WITHOUT case conversion
The implementation is straightforward:
toupper () { tr '[:lower:]' '[:upper:]'; }
tolower () { tr '[:upper:]' '[:lower:]'; }
case $1 in
--uppercase) shift; toupper;;
--lowercase) shift; tolower;;
*) cat;;
esac <<<"$#"
If the behavior is cemented, a way forward is to create a command with a different name with the same core behavior but with better command-line semantics, and eventually phase out the old version with the clumsy argument handling.

Related

Programmatically create bash command with flags for items in array

I have a list/array like so:
['path/to/folder/a', 'path/to/folder/b']
This is an example, the array can be of any length. But for each item in the array I'd like to set up the following as a single command:
$ someTool <command> --flag <item-1> --flag <item-2> ... --flag <item-N>
At the moment I am currently doing a loop over the array but I am just wondering if doing them individually has a different behaviour to doing them all at once (which the tool specifies I should do).
for i in "${array[#]}"; do
someTool command --flag $i
done
Whether passing all flag arguments to a single invocation of the tool does the same thing as passing them one-at-a-time to separate invocations depends entirely on the tool and what it does. Without more information, it's impossible to say for sure, but if the instructions recommend passing them all at once, I'd go with that.
The simplest way to do this in bash is generally to create a second array with the flags and arguments as they need to be passed to the tool:
flagsArray=()
for i in "${array[#]}"; do
flagsArray+=(--flag "$i")
done
someTool command "${flagsArray[#]}"
Note: all of the above syntax -- all the quotes, braces, brackets, parentheses, etc -- matter to making this run properly and robustly. Don't leave anything out unless you know why it's there, and that leaving it out won't cause trouble.
BTW, if the option (--flag) doesn't have to be passed as a separate argument (i.e. if the tool allows --flag=path/to/folder/a instead of --flag path/to/folder/a), then you can use a substitution to add the --flag= bit to each element of the array in a single step:
someTool command "${array[#]/#/--flag=}"
Explanation: the /# means "replace at the beginning (of each element)", then the empty string for the thing to replace, / to delimit that from the replacement string, and --flag= as the replacement (/addition) string.

Get the list/stack of commands, without using BASH_SOURCE and BASH_LINENO to read them from the executed files

Just like getting the function calls with ${FUNCNAME[#]}, is there a way to get the commands? BASH_COMMAND can only be used to get the last command (it's not an array, just a string).
I know I can achieve that by using BASH_SOURCE and BASH_LINENO to read the right line from the right file, but it does not work in case of evals (see my other, less-specific question Get the contents of an expanded expression given to eval through Bash internals)
Is there another way?
What is your intent? If you want to print a stack trace, you can use the Bash builtin command caller, like this:
dump_trace() {
local frame=0 line func source n=0
while caller "$frame"; do
((frame++))
done | while read line func source; do
((n++ == 0)) && {
printf 'Stack trace:\n'
}
printf '%4s at %s\n' " " "$func ($source:$line)"
done
}
From Bash manual:
caller [expr]
Returns the context of any active subroutine call (a shell function or
a script executed with the . or source builtins).
Without expr, caller displays the line number and source filename of
the current subroutine call. If a non-negative integer is supplied as
expr, caller displays the line number, subroutine name, and source
file corresponding to that position in the current execution call
stack. This extra information may be used, for example, to print a
stack trace. The current frame is frame 0.
The return value is 0 unless the shell is not executing a subroutine
call or expr does not correspond to a valid position in the call
stack.
See the full logging/error handling implementation here:
https://github.com/codeforester/base/blob/master/lib/stdlib.sh
Simple answer: there's no way to do that in Bash.
Related to the linked question and eval: Zsh seems to handle evals better, with variables and arrays such as EVAL_LINENO, zsh_eval_context and others.
funcstack
This array contains the names of the functions, sourced files, and (if EVAL_LINENO is set) eval commands. currently being executed. The first element is the name of the function using the parameter.
The standard shell array zsh_eval_context can be used to determine the type of shell construct being executed at each depth: note, however, that is in the opposite order, with the most recent item last, and it is more detailed, for example including an entry for toplevel, the main shell code being executed either interactively or from a script, which is not present in $funcstack.
See man zshall for more details.

How to get bash arguments with leading pound-sign (octothorpe)

I need to process an argument to a bash script that might or might not have a leading pound sign (octothorpe). The simplest example is:
echo #1234
which returns nothing
It might be because it processes the text as a command and assumes it is a comment.
$#, $*, etc. do not work. getopts does not seem to address this sort of thing.
Suggestions welcome
This is completely impossible, because the "argument" in question is parsed as a comment and never passed to the command at all.
Keep in mind that programs in C have the following calling convention for their main function:
int main(int argc, char *argv[])
This means that programs are passed a list of individual, separate arguments, not a single string that isn't yet parsed. The original string from which that vector of arguments was parsed is not given to the invoked program at all; often, no "original string" even exists. Consequently, a program that was invoked has no way to "unring the bell" and go back from the parsed list of strings to the original string from which it was generated.
Consequently, if your script is invoked as an external command (as opposed to a shell function), the invocation of the shell that runs it by the operating system will go through the execve syscall, which takes as its arguments (1) the file to execute; (2) the argument vector to pass it (which is to say, the aforementioned list of individual C strings); and (3) a list of environment variables. There is no argument for an unparsed shell command line, so no such content is available to the subprocess.
Train your users to use appropriate quoting. All of the below will have completely indistinguishable behavior, insofar as yourscript is concerned:
yourscript '#1234' # single quotes prevent content from being parsed as shell syntax
yourscript ''#1234 # "#" only begins a comment at the front of a string
yourscript '#'1234 # note that shell quoting is character-by-character
yourscript \#1234 # ...so either quoting or escaping only that single character suffices.
...any of the above will pass an argv containing (in C syntax) char[][]{ "yourscript", "#1234", NULL }

Why expanding on left hand side arg is leading to error

I accidentally wrote next assignment in one of my scripts:
$X=$(echo 'astring')
which fails with =astring: command not found.
The correct and intended assignment was X=$(echo 'astring') which works and sets X='astring'.
The question is what happens with the first one? Is $ trying to execute the result of the right hand side? And if that is so then why is it also incorporating = in it? I'm confused.
The behaviour of $X=$(echo 'astring') depends on the contents of $X. When it's empty (which it probably was), it expands to an empty string, and the remaining string is interpreted as a command
$X=$(echo 'astring')
=astring
If $X contains something, e.g. "astring", the string is expanded to
astring=astring
But it doesn't set the $astring variable as one might think, because of the order of expansions. Assignments are identified before any expansion happens. So, it's interpreted as a command again
astring=astring: command not found

Bash command line parsing containing whitespace

I have a parse a command line argument in shell script as follows:
cmd --a=hello world good bye --b=this is bash script
I need the parse the arguments of "a" i.e "hello world ..." which are seperated by whitespace into an array.
i.e a_input() array should contain "hello", "world", "good" and "bye".
Similarly for "b" arguments as well.
I tried it as follows:
--a=*)
a_input={1:4}
a_input=$#
for var in $a_input
#keep parsing until next --b or other argument is seen
done
But the above method is crude. Any other work around. I cannot use getopts.
The simplest solution is to get your users to quote the arguments correctly in the first place.
Barring that you can manually loop until you get to the end of the arguments or hit the next --argument (but that means you can't include a word that starts with -- in your argument value... unless you also do valid-option testing on those in which you limit slightly fewer -- words).
Adding to Etan Reisners answer, which is absolutely correct:
I personally find bash a bit cumbersome, when array/string processing gets more complex, and if you really have the strange requirement, that the caller should not be required to use quotes, I would here write an intermediate script in, say, Ruby or Perl, which just collects the parameters in a proper way, wraps quoting around them, and passes them on to the script, which originally was supposed to be called - even if this costs an additional process.
For example, a Ruby One-Liner such as
system("your_bash_script here.sh '".(ARGV.join(' ').split(' --').select {|s| s.size>0 }.join("' '"))."'")
would do this sanitizing and then invoke your script.

Resources