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.
Related
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.
It is very common to have a keyword that tests a similar behaviour, with a slightly different semantic like so:
The ${element_a} (gets updated with reference|has a reference) to the ${element_b}
However I wasn't able to find this feature documented anywhere. I know other libraries for testing such as Cucumber support matching steps through regexp, and I imagine since robotframework support embedded arguments in keyword names this can also work in robot.
Is there a way to write a keyword once and re-use it in slightly different situations?
*** Keywords ***
The ${element_a} ${updated_or_has} to the ${element_b}
IF """${update_or_has}""" == 'gets updated with reference'
Do Actions For Updated
ELSE IF """${update_or_has}""" == 'has a reference'
Do Other Actions
ELSE
Fail Unsupported value
END
That's one way to do it, branch the execution based on what string was part of the keyword name.
Calling it The element_a gets updated with reference to the element_b will have the keyword take one path, while The element_y has a reference to the element_z - a different one.
By the way, you might reconsider changing the verbing slightly, as the framework will have a bit hard time distinguishing what substring goes in the variable ${element_a} and what in ${updated_or_has} - put a static word, that'll be a part of the keyword name & act as a separator b/n the two vars.
Based on the comments, the branching is not needed, just a confirmation the caller used one of the two possible/supported values. This can be done with this check:
Run Keyword If """${update_or_has}""".lower() not in ('gets updated with reference', 'has a reference', ) Fail Unsupported value
You can specify an argument with a regular expression. So, you could treat part you want to match against as if it was an argument.
From the official user guide, in a section titled Using custom regular expressions:
A custom embedded argument regular expression is defined after the base name of the argument so that the argument and the regexp are separated with a colon.
In your particular case it might look something like this:
The ${element_a} ${foo:gets updated with reference|has a reference} to the ${element_b}
${foo} will be a variable that has whatever that little part is, which you can just ignore.
Here is a working example:
*** Keywords ***
The ${element_a} ${foo:gets updated with reference|has a reference} to the ${element_b}
log the foo argument is '${foo}'
*** Test Cases ***
Example
The foo gets updated with reference to the bar
The bar has a reference to the foo
# verify that the keyword only matches those two variations
Run keyword and expect error
... No keyword with name 'The something blah blah to the yada' found.
... The something blah blah to the yada
In the man page for the GNU version of find, at the end of the
EXPRESSIONS: ACTIONS: -printf section, is the following perplexing line:
A '%' at the end of the format
argument causes undefined behaviour since there is no following
character. In some locales, it may hide your door keys, while in
others it may remove the final page from the novel you are
reading.
I like the imagery, but what the does this actually mean? The find utility does not actually allow such a printf argument to be processed:
> find -printf "%"
find: error: % at end of format string
Inspection of the source code for reasonably recent versions of GNU find shows that it checks for this condition (a trailing %) and aborts with the error you indicate, so the excerpt from the man page appears to be nonsense.
I honestly can't imagine it ever behaved otherwise, so I doubt this excerpt was ever true.
My best guess is that the person writing the documentation somehow got it into their head that find's printf formats were somehow passed directly to the C library's printf function (which is ridiculous) and that the C library code might take some bizarre, locale-dependent action (which also seems ridiculous).
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.
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.