Bourne shell scripts with user input - unix

I'm trying to teach myself the basics of Bourne shell scripting using a textbook I borrowed from the library, and I'm working through the questions at the end of each chapter. However, I just got to one and I'm stumped...
Write a script that takes zero or more arguments and prints the last argument in the list. For example, given the argument 'myProgram arg1 arg2 arg3', the output would be 'arg3'.
Could anyone give me some advice on how to set this one up? I'm trying to review the section on user input and arguments, but I haven't worked with that much so far, so I don't have much practice yet.

echo ${!#} # bash only
eval echo \${$#} # sh-compatible
Explanation
The number of arguments is $#. Variables can be accessed indirectly via ${!VAR}. For example:
$ VAR="PATH"
$ echo ${!VAR}
/sbin:/bin:/usr/sbin:/usr/bin
Put those together and if we have a variable $n containing an integer we can access the $nth command-line argument with ${!n}. Or instead of $n let's use $#; the last command-line argument is ${!#}!
Additionally, this can be more longwindedly written using array slicing ($# is an array holding all the command-line arguments) as:
echo ${#:$#:$#}
Oddly, you cannot use an array index:
# Does not work
echo ${#[$#]}

I'll just give you some pointers. Since you want to learn bash, you probably don't just want a piece of code that does what the question asks:
1) Do you know how to count how many arguments your bash function has?
2) Do you know how to loop?
3) Do you know how to "pop" one of the arguments?
4) Do you know how to print out the first argument?
If you put all that together, I bet you'll come up with it.

Related

Is it possible to define a custom "#" escape functions in jq?

We have a number of builtin format/escape functions: #csv, #sh, etc
… | #sh
#sh "\( … )"
It is possible to define a custom format/escape function, say #sql?
Um. Technically, yes. I don't think I could recommend it, but it does appear to be possible.
First of all, you need to understand that #csv, #sh etc aren't separate functions from jq's point of view. They're all implemented by the format/1 function. This function takes a single string argument that is the name of the format to use, e.g. "csv" or "sh". You can redefine this function, and lo and behold jq will use it for formatting!
jq -n 'def format(fstring): "XXX";[1,2,3]|#json'
"XXX"
Okay, that's not very useful. How about this?
jq -n '
def format(fstring):
if fstring=="sparkle" then
".:!" + (tostring) + "!:."
else
oldformat(fstring)
end
;
[1,2,3]|#sparkle
'
".:![1,2,3]!:."
It worked! But don't get too excited...
jq -n '
def format(fstring):
if fstring=="sparkle" then
".:!" + (tostring) + "!:."
else
oldformat(fstring)
end
;
[1,2,3]|#json
'
Uh oh, that just hangs. Although we were hoping to delegate to the original format when we don't know what to do with the format, we actually called our new format function recursively. It just keeps calling itself forever. It looks like we might be out of luck on our extremely cursed quest. However, if we read the jq manual carefully, there is a glimmer of hope:
Multiple definitions using the same function name are allowed. Each re-definition replaces the previous one for the same number of function arguments, but only for references from functions (or main program) subsequent to the re-definition. See also the section below on scoping.
Great! We can use this to save the old format function:
jq -n '
def oldformat(fstring):
format(fstring);
def format(fstring):
if fstring=="sparkle" then
".:!" + (tostring) + "!:."
else
oldformat(fstring)
end
;
[1,2,3]|(#sparkle,#json,#sh)
'
".:![1,2,3]!:."
"[1,2,3]"
"1 2 3"
I really don't recommend this: it's awkward to do, not terribly useful, and I have no idea if it's going to break something else. The format function doesn't appear to be documented so it's likely an unsupported implementation detail. But it was fascinating to find out that this is technically possible.

zsh accepts syntactically incorrect loop construct

By mistake, I typed something like this:
for f in a b; echo a; echo x
The output produced was
a
a
x
as if I had written
for f in a b; do echo a; done; echo x
Could someone explain, why my code produced this example? Checking the man page, it clearly says that the required syntax has to be
for name ... [ in word ... ] term do list done
No shortcut explains that I could leave out the do or done.
I'm running zsh 5.8
Somewhat confusingly, that syntax is not described in the section that introduces the for loop. Instead it's listed with some other short command versions, in a separate part of the zshmisc man page titled ALTERNATE FORMS FOR COMPLEX COMMANDS.
for name ... [ in word ... ] term sublist
where term is at least one newline or ;. Another short form of for.
The introduction to the section mentions some caveats:
Many of zsh's complex commands have alternate forms. These
are non-standard and are likely not to be obvious even to seasoned
shell programmers; they should not be used anywhere that portability
of shell code is a concern.
The short versions below only work if sublist is of the form `{ list
}' or if the SHORT_LOOPS option is set.

Printing unexpanded recursive variables in gnu make

Is there a way to print the unexpanded definition of a recursive variable? I have a complicated build system, and a user can set some values. I'd like to echo the user definition to another file, for later use.
For example,
externals = $(HOME)/externals
all:
echo $(externals)
doesn't work, because it echos using the current definition of HOME. I'd like it to echo
the literal string $(HOME)/externals without expanding $(HOME).
The value function is probably what you want here.
8.8 The value Function
The value function provides a way for you to use the value of a variable without having it expanded. Please note that this does not undo expansions which have already occurred; for example if you create a simply expanded variable its value is expanded during the definition; in that case the value function will return the same result as using the variable directly.
The syntax of the value function is:
$(value variable)
Note that variable is the name of a variable, not a reference to that variable. Therefore you would not normally use a ‘$’ or parentheses when writing it. (You can, however, use a variable reference in the name if you want the name not to be a constant.)
The result of this function is a string containing the value of variable, without any expansion occurring. For example, in this makefile:
FOO = $PATH
all:
#echo $(FOO)
#echo $(value FOO)
The first output line would be ATH, since the “$P” would be expanded as a make variable, while the second output line would be the current value of your $PATH environment variable, since the value function avoided the expansion.
The value function is most often used in conjunction with the eval function (see Eval Function).
Though in addition to this you are going to need to use single quotes on that echo line or the shell will expand things on you.
$ cat Makefile
externals = $(HOME)/externals
all:
echo $(externals)
allv:
echo $(value externals)
allvq:
echo '$(value externals)'
$ make all
echo /home/user/externals
/home/user/externals
$ make allv
echo $(HOME)/externals
/bin/sh: HOME: command not found
/externals
$ make allvq
echo '$(HOME)/externals'
$(HOME)/externals

/usr/bin/Rscript: Argument list too long

Using rscript inside a bash script
I am passing the content of text files has arguments. to rscript
"$SCRIPTS/myscript.R" "$filecontent"
I get the following when file have +- over 4000 row
/usr/bin/Rscript: Argument list too long
Any way I can increase the length of accepted argument so I can pass large files?
What #MrFlick said is correct - you should change the way the arguments are passed to your script. However, if you still want to try to do it your way, then I recommend reading the following article:
"Argument list too long": Beyond Arguments and Limitations
The "Argument list too long" error, which occurs anytime a user feeds
too many arguments to a single command, leaves the user to fend for
oneself, since all regular system commands (ls *, cp *, rm *, etc...)
are subject to the same limitation. This article will focus on
identifying four different workaround solutions to this problem, each
method using varying degrees of complexity to solve different
potential problems.
Also, this Unix&Linux thread can help:
“Argument list too long”: How do I deal with it, without changing my command?

In zsh pass hash as parameter

What is the correct / idiomatic way of passing a hash to a function?
I have sort of hit upon this but am not sure how clean this is or if there any pitfalls.
typeset -A hash
hash=(a sometext b moretext)
foo hash
foo() {
typeset -A mhash
mhash=( ${(Pkv)1} )
}
The P flag interprets result (in this case $1 as holding a parameter name). Since this resulted in only getting the values and not the keys, I bolted on the "kv" to get both keys and values.
Is this the correct way, or is there another way. btw, since i am passing an array and a hash in my actual program, I don't want to use "$*" or "$#"
I tried a little and i'm not sure there is an other way than using $# on the function.
Re: Array as parameter - Zsh mailing list
Possible answers in these questions (bash-oriented):
How to pass an associative array as argument to a function in Bash?
Passing arrays as parameters in bash
Passing array to function of shell script
In fact, when you start needing to use an array, or even worse, an associative array in a shell script, maybe it's time to switch to a more powerful script language, like perl or python.
If you don't do it for you, do it for you 6 months from now / for your successors.

Resources