Semicolon separated aliases seems to break argument references (tcsh) - tcsh

I create two aliases as follows:
alias echo1 'echo \!#:1'; - Echo first argument
alias echo2 'echo \!#:2'; - Echo second argument
then echo1 foo fee -> foo
and echo2 foo fee -> fee
however,
echo1 fee; echo2 fuu fii; -> fee (only)
while,
echo2 fuu fii; echo1 fee; -> fii, fii
It appears that when aliases are put on a single line, separated by a semicolon, the arguments for the second command come from the output of the first command, rather than using the second set of arguments. That seems counter-intuitive to me. I would expect two commands separated by a semicolon to produce the same result as if they were issued on separate lines, but this is apparently not the case. Seems like there is an implied pipe. Does anyone know why?
This same behavior occurs when defining a single alias with two comma separated commands. I can't figure out how to make each command index the arguments that follow it.

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.

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 doesn't bash IFS value split expansion argument?

>export FOOBAR=foobar; IFS=b echo ${FOOBAR}
I was expecting to see
foo ar
but I see
foobar
Why?
The IFS hasnt yet taken effect. add another ";":
FOOBAR=foobar IFS=b; echo ${FOOBAR}
In man bash section SIMPLE COMMAND EXPANSION
you can read (abbreviated):
When a simple command is executed
The words that the parser has marked as variable assignments (those preceding the command name) are saved for later processing.
The words that are not variable assignments or redirections are expanded.
...
The text after the = in each variable assignment ... [are] assigned to the variable.
so the IFS=b is done after expanding $FOOBAR.
[edit]I removed the technically incorrect answer.
http://tldp.org/LDP/abs/html/internalvariables.html
"This variable determines how Bash recognizes fields, or word boundaries, when it interprets character strings."

Remove last argument from argument list of shell script (bash)

This question concerns a bash script that is run in automator osx. I am using automator actions to get and filter a bunch of file references from the finder. Then I append to that list the name of the parent folder, also via an automator action. Automator then feeds these arguments to an action called "run shell script". I am not sure exactly how automator invokes the script but the argument list looks like this when echoed with: echo "$#"
/Volumes/G-Raid/Online/WAV_TEST/Testbok 50/01/01000 43-001.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50/02/02000 43-002.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50/03/03000 43-003.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50
In this case path to 3 files and a folder.
In the shell script I launch an application called ripcheckc* with the args passed from automator minus the last argument(the folder) in the list.
I use this to remove the last argument:
_args=( "$#" )
unset _args[${#_args[#]}-1]
And this is echo $_args:
/Volumes/G-Raid/Online/WAV_TEST/Testbok 50/01/01000 43-001.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50/02/02000 43-002.wav /Volumes/G-Raid/Online/WAV_TEST/Testbok 50/03/03000 43-003.wav
Same as before but without the folder.
Now, if I run ripcheckc with "$#" as argument it works (but fails later on because of that last path in the argument list) If I use ${_args[#]} the application will just abort silently. When I echo $# and _args the output looks identical except for the last argument.
My question is - what is the difference between $# and $_args that make the first valid input and the second not?
*The application is ripcheckc
I hope my question makes sense.
EDIT: Solved.
I have used this bash one-liner before
set -- "${#:1:$(($#-1))}"
It sets the argument list to the current argument list, less the last argument.
How it works:
$# is the number of arguments
$((...)) is an arithmetic expression, so $(($#-1)) is one less than the number of arguments.
${variable:position:count} is a substring expression: it extracts count characters from variable starting at position. In the special case where variable is #, which means the argument list, it extracts count arguments from the list beginning at position. Here, position is 1 for the first argument and count is one less than the number of arguments worked out previously.
set -- arg1...argn sets the argument list to the given arguments
So the end result is that the argument list is replaced with a new list, where the new list is the original list except for the last argument.
Assuming that you already have an array, you can say:
unset "array[${#array[#]}-1]"
For example, if your script contains:
array=( "$#" )
unset "array[${#array[#]}-1]" # Removes last element -- also see: help unset
for i in "${array[#]}"; do
echo "$i"
done
invoking it with: bash scriptname foo bar baz produces:
foo
bar
You can also get all but the last argument with
"${#:0:$#}"
which, honestly, is a little sketchy, since it seems to be (ab)using the fact that arguments are numbered starting with 1, not 0.
Update: This only works due to a bug (fixed in 4.1.2 at the latest) in handling $#. It works in version 3.2.
set -- "${#:1:$#-1}"
sets the parameter list to first up to penultimate, removing the last one

Theory: who can explain the use of =

can someone explain me with this code
data=$(date +"%Y-%m-%dS%H:%M:%S")
name="/home/cft/"$data"_test.tar"
touch $name
works, creating a new .tar file but this code doesn't work
data=$(date +"%Y-%m-%dS%H:%M:%S")
name= "/home/cft/"$data"_test.tar"
touch $name
and gives me this error: no such file or directory?
why the space between = and inverted commas creates this error?
Shell allows you to provide per-command environment overrides by prefixing the command with one or more variable assignments.
name= "/home/cft/"$data"_test.tar"
asks the shell to run the program named /home/cft/2013-10-08S12:00:00_test.tar (for example) with the value of name set to the empty string in its environment.
(In your case, the error occurs because the named tar file either doesn't exist or, if it does, is not an executable file.)
A variable assignment is identified by having no whitespace after the equal sign.
(name = whatever, of course, is simply a command called name with two string arguments, = and whatever.)
You can't have whitespace between the equal sign and the definition.
http://www.tldp.org/LDP/abs/html/varassignment.html
There is no theory behind this. It's just a decision the language designers made, and which the parser enforces.
In BASH (and other Bourne type shells like zsh and Kornshell), the equal sign cannot have spaces around it when setting variables.
Good:
$ foo="bar"
Bad:
$ foo= "bar"
$ foo = "bar"
There's no real reason that would prevent spaces from being used. Other programming languages have no problems with this. It's just the syntax of the shell itself.
The reason might be related to the original Bourne shell parsing where the shell would break up a command line based upon whitespace. That would make foo=bar a single parameter instead of two or three (depending if you have white space on both sides or just one side of the equal sign). The shell could see the = sign, and know this parameter is an assignment.
The shell parameter parsing is very primitive in many ways. Whitespace is very important. The shell has to be small and fast in order to be responsive. That means stripping down unessential things like complex line parsing.
Inverted commas I believe you mean quotation marks. Double quotes are used to override the breaking out of parameters over white space:
Bad:
$ foo=this is a test
bash: is: command not found
Good:
$ foo="this is a test"
Double quotes allow interpolation. Single quotes don't:
$ foo="bar"
$ echo "The value of foo is $foo"
The value of foo is bar
$ echo 'The value of foo is $foo'
The value of foo is $foo.
If you start out with single quotes, you can put double quotes inside. If you have single quotes, you can put double quotes inside.
$ foo="bar"
$ echo "The value of foo is '$foo'"
The value of foo is 'bar'
$ echo 'The value of foo is "$foo"'
The value of foo is "$foo"
This means you didn't have to unquote $data. However, you would have to put curly braces around it because underscores are legal characters in variable names. Thus, you want to make sure that the shell understand that the variable is $data and not $data_backup:
name="/home/cft/${data}_test.tar"

Resources