Is there a concise way to substitute return value of a function? - dash-shell

I'm putting together some unit tests for a dash shell script I've inherited and, given the large number of tests we want to add, I want to keep them as concise as possible.
To do this, I've added a check helper function to handle pass or failure of a test. It basically receives the return code $? from the previous command and passes or fails the test based on that. This allows me to simply implement a one-liner test (of which there are many) as:
for td in 1.1.1.999 {{lots of other invalid IPs}} ; do
isInvalidIpV4 ${td} ; check is-true $? "Checking ${td} is invalid"
done
for seg in 0 1 9 10 99 100 254 255 ; do
td="${seg}.${seg}.${seg}.${seg}"
isInvalidIpV4 ${td} ; check is-false $? "Checking ${td} is valid"
done
Now this is reasonably good since it gets each test down to a single line but, in my OCD quest for perfection, I wonder if there's even more scope for reduction.
I know that, like bash, there is the capability to run a command and use its output in another command, along the lines of:
echo "Lowercase name is $(echo PaxDiablo | tr '[A-Z]' '[a-z]')"
but I was wondering if there were a way to do this same substitution with the return code of a command.
That would mean my test line would be able to have that annoyingly repeated $? removed, changing from:
isInvalidIpV4 ${td} ; check pass $? "${td} should be invalid"
to something like:
check pass $[isInvalidIpV4 ${td}] "${td} should be invalid"
where $[] is the mythical "use return code" substitution operator.
Does dash have this facility or is there a more concise way to do it than what I've currently done?
Just keep in mind, other shells are not an option here, this is an embedded system for which dash is the only shell provided.

if ! isInvalidIpV4 $td
then
echo $td should be invalid
fi

Related

difference in behavior of "tr" command on "dash" (-) between busybox and Ubuntu/Raspbian/etc

I have a function in a script which is used to validate that input strings don't contain any unacceptable characters. In this case, allowable characters are alpha, numeric, underscore, dash, period, and space.
#!/bin/sh
pattern="\_\-\. [a-zA-Z0-9]"
while [ 1 ]; do
echo "enter your test string"
read string
echo "result:"
echo "$string" | tr -cd "$pattern" | sed 's/\[//' | sed 's/\]//'
echo
echo
done
Testing on Raspbian (Raspberry Pi):
pi#raspberrypi:~ $ ./trtest.sh
enter your test string
dash-dash
result:
dash-dash
enter your test string
under_score
result:
under_score
Testing on an Onion board (OpenWRT/busybox):
root#Omega-FD22:~# ./trtest.sh
enter your test string
dash-dash
result:
dashdash <<<----- I'm not expecting this
enter your test string
under_score
result:
under_score
So,
#1 I am not sure why there is a difference in behavior between "tr" in these two cases, specifically on the "dash" character.
#2 If there's another way to do this, I'm open to it.
Thanks for any insights.
DL
FYI one of my diligent colleagues figured it out, so I am passing on his solution. If you move "\-" to the end of the pattern matching string, then it works in both environments. Somewhat beyond my ability to explain are the technical/philosophical underpinnings of this, but I'm glad it works.
Before:
pattern="\_\-\. [a-zA-Z0-9]"
After:
pattern="\_\. [a-zA-Z0-9]\-"

How to get the return value of instruments?

I'm using instruments inside a bash script on a continuous integration server.
I would like to know when a command as failed in the script, so I can exit prematurely from it and mark the build as failed.
instruments displays a LOG ERROR to the console, but I don't succeed in getting a return value.
How can I achieve this?
if have:
instruments -w "iPhone 6 (8.3 Simulator)" -t
it is possible to do something like:
if(...)
then ...
Thanks in advance
Fairly easy:
instruments -w "iPhone 6 (8.3 Simulator)" -t
if (( $? > 0 ))
then
echo "instruments commands failed with error: $?" >&2
fi
The (( )) notation is for an arithmetic comparison. String pattern comparisons are done using [[ ... ]]. Be careful to use the correct spacing, in general whitespace is used as a separator in shells, so it can be significant.
An alternative syntax could be:
if instruments -w "iPhone 6 (8.3 Simulator)" -t
then
echo "it worked"
else
echo "it failed"
fi
and that is often preferable. But I think in this case the style I show fits better with what you need.
The special variable ? gives the return value of the previous command. Prefixing with a $ gives us the variable's value. By convention, a return value of zero means success, 1-255 means an error (the range on UNIX/Linux is 0-255, one byte). The significance of each error number is application specific, so you must read the documentation to find what it means.
Remember that $? gives us the return value of the previous command, so even an echo would reset it!
The >&2 means "send the output to the standard error stream". Error messages should go here, which is also known s stderr, file descriptor 2. It is a nice thing to do if you are redirecting output from the script.
EDIT: After all that, Apple does not appear to document an exit code for the instruments command, I checked the man page. That's poor in my opinion, but there's not much you can do. In languages like C (and probably ObjC) if the program ends without setting an exit code you just get any old value that is lying about in memory. So you cannot even rely on zero being success - unless you know otherwise?

How to make the output of Maxima cleaner?

I want to make use of Maxima as the backend to solve some computations used in my LaTeX input file.
I did the following steps.
Step 1
Download and install Maxima.
Step 2
Create a batch file named cas.bat (for example) as follows.
rem cas.bat
echo off
set PATH=%PATH%;"C:\Program Files (x86)\Maxima-5.31.2\bin"
maxima --very-quiet -r %1 > solution.tex
Save the batch in the same directory in which your input file below exists. It is just for the sake of simplicity.
Step 3
Create the input file named main.tex (for example) as follows.
% main.tex
\documentclass[preview,border=12pt,12pt]{standalone}
\usepackage{amsmath}
\def\f(#1){(#1)^2-5*(#1)+6}
\begin{document}
\section{Problem}
Evaluate $\f(x)$ for $x=\frac 1 2$.
\section{Solution}
\immediate\write18{cas "x: 1/2;tex(\f(x));"}
\input{solution}
\end{document}
Step 4
Compile the input file with pdflatex -shell-escape main and you will get a nice output as follows.
!
Step 5
Done.
Questions
Apparently the output of Maxima is as follows. I don't know how to make it cleaner.
solution.tex
1
-
2
$${{15}\over{4}}$$
false
Now, my question are
how to remove such texts?
how to obtain just \frac{15}{4} without $$...$$?
(1) To suppress output, terminate input expressions with dollar sign (i.e. $) instead of semicolon (i.e. ;).
(2) To get just the TeX-ified expression sans the environment delimiters (i.e. $$), call tex1 instead of tex. Note that tex1 returns a string, which you have to print yourself (while tex prints it for you).
Combining these ideas with the stuff you showed, I think your program could look like this:
"x: 1/2$ print(tex1(\f(x)))$"
I think you might find the Maxima mailing list helpful. I'm pretty sure there have been several attempts to create a system such as the one you describe. You can also look at the documentation.
I couldn't find any way to completely clean up Maxima's output within Maxima itself. It always echoes the input line, and always writes some whitespace after the output. The following is an example of a perl script that accomplishes the cleanup.
#!/usr/bin/perl
use strict;
my $var = $ARGV[0];
my $expr = $ARGV[1];
sub do_maxima_to_tex {
my $m = shift;
my $c = "maxima --batch-string='exptdispflag:false; print(tex1($m))\$'";
my $e = `$c`;
my #x = split(/\(%i\d+\)/,$e); # output contains stuff like (%i1)
my $f = pop #x; # remove everything before the echo of the last input
while ($f=~/\A /) {$f=~s/\A .*\n//} # remove echo of input, which may be more than one line
$f =~ s/\\\n//g; # maxima breaks latex tokens in the middle at end of line; fix this
$f =~ s/\n/ /g; # if multiple lines, get it into one line
$f =~ s/\s+\Z//; # get rid of final whitespace
return $f;
}
my $e1 = do_maxima_to_tex("diff($expr,$var,1)");
my $e2 = do_maxima_to_tex("diff($expr,$var,2)");
print <<TEX;
The first derivative is \$$e1\$. Differentiating a second time,
we get \$$e2\$.
TEX
If you name this script a.pl, then doing
a.pl z 3*z^4
outputs this:
The first derivative is $12\,z^3$. Differentiating a second time,
we get $36\,z^2$.
For the OP's application, a script like this one could be what is invoked by the write18 in the latex file.
If you really want to use LaTeX then the maxiplot package is the answer. It provides a maxima environment inside of which you enter Maxima commands. When you process your LaTeX file a Maxima batch file is generated. Process this file with Maxima and process your LaTeX file again to typeset the equations generated by Maxima.
If you would rather have 2D math input with live typesetting then use TeXmacs. It is a cross-platform document authoring environment (a word processor on steroids if you like) that includes plugins for Maxima, Mathematica and many more scientific computing tools. If you need to or are not satisfied with the typesetting, you can export your document to LaTeX.
I know this is a very old post. Excellent answers for the question asked by OP. I was using --very-quiet -r options on the command line for a long time like OP, but in maxima version 5.43.2 they behave differently. See maxima command line v5.43 is behaving differently than v5.41. I am answering this question with a cross reference because when incorporating these answers in your solutions, make sure the changes in behavior of those command line flags are also incorporated.

What tools deal with spaces in columnar data well?

Let's start with an example that I ran into recently:
C:\>net user
User accounts for \\SOMESYSTEM
-------------------------------------------------------------------------------
ASPNET user1 AnotherUser123
Guest IUSR_SOMESYSTEM IWAM_SOMESYSTEM
SUPPORT_12345678 test userrrrrrrrrrrr test_userrrrrrrrrrrr
The command completed successfully.
In the third row, second column there is a login with a space. This causes many of the tools that separate fields based on white space to treat this field as two fields.
How would you deal with data formatted this way using today's tools?
Here is an example in pure** Windows batch language on the command prompt that I would like to have replicated in other modern cross-platform text processing tool sets:
C:\>cmd /v:on
Microsoft Windows [Version 5.2.3790]
(C) Copyright 1985-2003 Microsoft Corp.
C:\>echo off
for /f "skip=4 tokens=*" %g in ('net user ^| findstr /v /c:"The command completed successfully."') do (
More? set record=%g
More? echo !record:~0,20!
More? echo !record:~25,20!
More? echo !record:~50,20!
More? )
ASPNET
user1
AnotherUser123
Guest
IUSR_SOMESYSTEM
IWAM_SOMESYSTEM
SUPPORT_12345678
test userrrrrrrrrrrr
test_userrrrrrrrrrrr
echo on
C:\>
** Using variable delayed expansion (cmd /v:on or setlocal enabledelayedexpansion in a batch file), the for /f command output parser, and variable substring syntax... none of which are well documented except for at the wonderful website http://ss64.com/nt/syntax.html
Looking into AWK, I didn't see a way to deal with the 'test userrrrrrrrrrrr' login field without using substr() in a similar method to the variable substring syntax above. Is there another language that makes text wrangling easy and is not write-only like sed?
PowerShell:
Native user list example, no text matching needed
Get-WmiObject Win32_UserAccount | Format-Table -Property Caption -HideTableHeaders
Or, if you want to use "NET USER":
$out = net user # Send stdout to $out
$out = $out[4..($out.Length-3)] # Skip header/tail
[regex]::split($out, "\s{2}") | where { $_.Length -ne 0 }
# Split on double-space and skip empty lines
Just do a direct query for user accounts, using vbscript (or powershell if your system supports)
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_UserAccount",,48)
For Each objItem in colItems
Wscript.Echo objItem.Name
Next
This will show you a list of users, one per line. If your objective is just to show user names, there is no need to use other tools to process thee data.
Awk isn't so great for that problem because awk is focused on lines as records with a recognizable field separator, while the example file uses fixed-width fields. You could, e.g., try to use a regular expression for the field separator, but that can go wrong. The right way would be to use that fixed width to clean the file up into something easier to work with; awk can do this, but it is inelegant.
Essentially, the example is difficult because it doesn't follow any clear rules. The best approach is a quite general one: write data to files in a well-defined format with a library function, read files by using a complementary library function. Specific language doesn't matter so much with this strategy. Not that that helps when you already have a file like the example.
TEST
printf "
User accounts for \\SOMESYSTEM
-------------------------------------------------------------------------------
ASPNET user1 AnotherUser123
Guest IUSR_SOMESYSTEM IWAM_SOMESYSTEM
SUPPORT_12345678 test userrrrrrrrrrrr test_userrrrrrrrrrrr
The command completed successfully.
\n" | awk 'BEGIN{
colWidth=25
}
/-----/ {next}
/^[[:space:]]*$/{next}
/^User accounts/{next}
/^The command completed/{next}
{
col1=substr($0,1,colWidth)
col2=substr($0,1+colWidth,colWidth)
col3=substr($0,1+(colWidth*2),colWidth)
printf("%s\n%s\n%s\n", col1, col2, col3)
}'
There's probably a better way than the 1+(colWidth*2) but I'm out of time for right now.
If you try to execute code as is, you'll have to remove the leading spaces at the front of each line in the printf statement.
I hope this helps.
For this part:
set record=%g
More? echo !record:~0,20!
More? echo !record:~25,20!
More? echo !record:~50,20!
I would use:
for /f "tokens=1-26 delims= " %a in (%g%) do (
if not "%a" = "" echo %a
if not "%b" = "" echo %b
if not "%c" = "" echo %c
rem ... and so on...
if not "%y" = "" echo %y
if not "%z" = "" echo %z
)
That is if I had to do this using batch. But I wouldn't dare to call this "modern" as per your question.
perl is really the best choice for your case, and millions of others. It is very common and the web is ripe with examples and documentation. Yes it is cross platform, extremely stable, and nearly perfectly consistent across platforms. I say nearly because nothing is perfect and I doubt in your lifetime that you would encounter an inconsistency.
It is a language interpreter but supports a rich command-line interface as well.

csh idioms to check for environment variable existence?

I've got a few csh scripts where I need to check that certain environment variables are set before I start doing stuff, so I do this sort of thing:
if ! $?STATE then
echo "Need to set STATE"
exit 1
endif
if ! $?DEST then
echo "Need to set DEST"
exit 1
endif
which is a lot of typing. Is there a more elegant idiom for checking whether or not an environment variable is already set?
Notes:
This question is quite similar, but specifically asks about solutions in bash.
I'm not looking for people to advise me to stay away from csh because it's cursed, scary, or bash is better. I'm specifically interested in a more elegant solution than what I'm using now.
I think the way you're doing it (an if statement with a condition using the $?VAR syntax, which evaluates to 1 if the variable is set, and 0 otherwise) is probably the most idiomatic csh construct that does what you want.
Try the following:
[ -z STATE ] && echo "Need to set STATE"
[ ! -z DEST ] && echo "Need to set STATE"

Resources