Min*_*nix 54 scripting numeric-data
我正在寻找一个命令来限制从stdin
.
我为此编写了一个小脚本(欢迎批评),但我想知道是否没有针对此简单且(我认为)常见用例的标准命令。
我的脚本找到了两个数字中的最小值:
#!/bin/bash
# $1 limit
[ -z "$1" ] && { echo "Needs a limit as first argument." >&2; exit 1; }
read number
if [ "$number" -gt "$1" ]; then
echo "$1"
else
echo "$number"
fi
Run Code Online (Sandbox Code Playgroud)
Dig*_*uma 120
如果您知道要处理两个整数a
and b
,那么这些使用三元运算符的简单shell 算术展开足以给出数值最大值:
$(( a > b ? a : b ))
Run Code Online (Sandbox Code Playgroud)
和数字最小值:
$(( a < b ? a : b ))
Run Code Online (Sandbox Code Playgroud)
例如
$ a=10
$ b=20
$ max=$(( a > b ? a : b ))
$ min=$(( a < b ? a : b ))
$ echo $max
20
$ echo $min
10
$ a=30
$ max=$(( a > b ? a : b ))
$ min=$(( a < b ? a : b ))
$ echo $max
30
$ echo $min
20
$
Run Code Online (Sandbox Code Playgroud)
这是一个 shell 脚本,演示了这一点:
#!/usr/bin/env bash
[ -z "$1" ] && { echo "Needs a limit as first argument." >&2; exit 1; }
read number
echo Min: $(( $number < $1 ? $number : $1 ))
echo Max: $(( $number > $1 ? $number : $1 ))
Run Code Online (Sandbox Code Playgroud)
gle*_*man 26
sort
并且head
可以这样做:
numbers=(1 4 3 5 7 1 10 21 8)
printf "%d\n" "${numbers[@]}" | sort -rn | head -1 # => 21
Run Code Online (Sandbox Code Playgroud)
mik*_*erv 22
您可以只比较两个数字,dc
例如:
dc -e "[$1]sM $2d $1<Mp"
Run Code Online (Sandbox Code Playgroud)
..."$1"
你的最大值在哪里,"$2"
如果它小于 ,你将打印的数字"$1"
。这也需要 GNU dc
- 但你可以像这样便携地做同样的事情:
dc <<MAX
[$1]sM $2d $1<Mp
MAX
Run Code Online (Sandbox Code Playgroud)
在上述两种情况下,您都可以将精度设置为 0 (默认值)以外的值,例如${desired_precision}k
. 对于这两个值,您还必须验证这两个值绝对是数字,因为dc
可以system()
使用!
操作员拨打电话。
使用以下小脚本(和下一个),您还应该验证输入 - 类似grep -v \!|dc
或稳健处理任意输入的东西。您还应该知道dc
用_
前缀而不是-
前缀解释负数- 因为后者是减法运算符。
除此之外,使用此脚本dc
将读取\n
您愿意提供的尽可能多的连续ewline 分隔数字,并为每个$max
值或输入打印,具体取决于哪个是 wo 中的较小者:
dc -e "${max}sm
[ z 0=? d lm<M p s0 lTx ]ST
[ ? z 0!=T q ]S?
[ s0 lm ]SM lTx"
Run Code Online (Sandbox Code Playgroud)
所以...每个那些的[
方括号]
广阔是一个dc
字符串是对象S
的每个AVED到其各自的阵列-中的任何一个T
,?
或M
。除了一些其他dc
可能与string 相关的事情之外,它还可以将x
一个作为宏执行。如果你安排得当,一个功能齐全的小dc
脚本就足够简单地组装起来了。
dc
在堆栈上工作。所有输入对象都堆叠在最后一个 - 每个新输入对象在添加时将最后一个顶部对象及其下方的所有对象向下推入堆栈。对对象的大多数引用都指向堆栈顶部值,并且大多数引用弹出堆栈顶部(将其下方的所有对象向上拉一个)。
除了主栈,还有(至少) 256个数组,每个数组元素都有一个栈。我在这里用的不多。我只是存储提到的字符串,以便我可以l
在需要时加载它们并x
有条件地执行它们,并且我s
将$max
的值放在m
数组的顶部。
无论如何,这一点dc
主要是您的shell 脚本所做的。它确实使用了 GNU-ism-e
选项 -dc
通常从标准输入中获取其参数 - 但您可以这样做:
echo "$script" | cat - /dev/tty | dc
Run Code Online (Sandbox Code Playgroud)
...如果$script
看起来像上面的位。
它的工作原理如下:
lTx
- 这l
oads 和x
ecutes 存储在顶部的宏T
(我猜是为了测试 - 我通常随意选择这些名称)。z 0=?
- T
est 然后测试堆栈深度 w/ z
,如果堆栈为空(读取:持有 0 个对象),则调用?
宏。? z0!=T q
- 该?
宏以?
dc
从 stdin 读取一行输入的内置命令命名,但我还z
向它添加了另一个堆栈深度测试,以便它可以q
在它拉入一个空行或命中 EOF 时适应整个小程序。但是如果它没有!
成功填充堆栈,它会T
再次调用est。d lm<M
- T
EST然后将d
uplicate堆栈的顶部,并比较它$max
(存储在m
)。如果m
是较小的值,则dc
调用M
宏。s0 lm
-M
只是弹出堆栈的顶部并将其转储到虚拟标量0
- 只是一种弹出堆栈的廉价方式。在返回est之前,它还会再次l
加载。m
T
p
- 这意味着如果m
小于当前堆栈的顶部,则m
替换它(d
无论如何是它的副本)并在此处被打印p
,否则它不会并且无论输入是什么都被p
打印。s0
- 之后(因为p
不弹出堆栈)我们0
再次将堆栈顶部转储,然后......lTx
-再次递归l
oad T
est 然后x
再次执行它。所以你可以运行这个小片段并在你的终端上交互式输入数字dc
,$max
如果你输入的数字更大,它就会打印回你输入的数字或值。它还可以接受任何文件(例如管道)作为标准输入。它将继续读取/比较/打印循环,直到遇到空行或 EOF。
不过,关于此的一些说明 - 我写这个只是为了模拟你的 shell 函数中的行为,所以它只能稳健地处理每行一个数字。dc
但是,可以处理每行尽可能多的空格分隔数字,只要你愿意扔它。但是,由于它的堆栈,一行上的最后一个数字会成为它操作的第一个数字,因此,dc
如果您在每行打印/键入多个数字,则按照所写的方式,将反向打印其输出。正确的方法处理它是将一行存储在一个数组中,然后对其进行处理。
像这样:
dc -e "${max}sm
[ d lm<M la 1+ d sa :a z0!=A ]SA
[ la d ;ap s0 1- d sa 0!=P ]SP
[ ? z 0=q lAx lPx l?x ]S?
[q]Sq [ s0 lm ]SM 0sa l?x"
Run Code Online (Sandbox Code Playgroud)
但是......我不知道我是否想尽可能深入地解释这一点。可以这么说,当dc
读取堆栈上的每个值时,它会将其值或$max
的值存储在索引数组中,并且一旦检测到堆栈再次为空,它就会在尝试读取另一个对象之前打印每个索引对象输入行。
因此,虽然第一个脚本确实......
10 15 20 25 30 ##my input line
20
20
20
15
10 ##see what I mean?
Run Code Online (Sandbox Code Playgroud)
第二个是:
10 15 20 25 30 ##my input line
10 ##that's better
15
20
20 ##$max is 20 for both examples
20
Run Code Online (Sandbox Code Playgroud)
如果您首先使用k
命令设置它,您可以处理任意精度的浮点数。并且您可以独立地更改i
输入或o
输出基数 - 这有时会因您可能意想不到的原因而有用。例如:
echo 100000o 10p|dc
00010
Run Code Online (Sandbox Code Playgroud)
...首先将dc
的输出基数设置为 100000,然后打印 10。
评论太长了:
虽然您可以使用sort | head
或sort | tail
组合来做这些事情,但在资源和错误处理方面似乎都不太理想。就执行而言,组合意味着生成 2 个进程只是为了检查两行。这似乎有点矫枉过正。
更严重的问题是,在大多数情况下,您需要知道输入是健全的,即仅包含数字。@glennjackmann 的解决方案巧妙地解决了这个问题,因为printf %d
应该对非整数表示不满。它也不适用于浮点数(除非您将格式说明符更改为%f
,否则会遇到舍入问题)。
test $1 -gt $2
会告诉你比较是否失败(退出状态为 2 意味着在测试过程中出现错误。由于这通常是一个内置的 shell,没有产生额外的进程 - 我们谈论的是数百个顺序执行速度快了几倍。不过,仅适用于整数。
如果您碰巧需要比较几个浮点数,有趣的选项可能是bc
:
define x(a, b) {
if (a > b) {
return (a);
}
return (b);
}
Run Code Online (Sandbox Code Playgroud)
将等效于test $1 -gt $2
, 并在 shell 中使用:
max () { printf '
define x(a, b) {
if (a > b) {
return (a);
}
return (b);
}
x(%s, %s)
' $1 $2 | bc -l
}
Run Code Online (Sandbox Code Playgroud)
仍然比printf | sort | head
(两个数字)快近 2.5 倍。
如果您可以依赖 GNU 扩展bc
,那么您还可以使用该read()
函数将数字直接读入bc
sript。
小智 6
您可以定义一个预定义数学函数库,bc
然后在命令行中使用它们。
例如,在文本文件中包含以下内容,例如~/MyExtensions.bc
:
define max(a,b){
if(a>b)
{
return(a)
}else{
return(b)
}
}
Run Code Online (Sandbox Code Playgroud)
现在您可以bc
通过以下方式调用:
> echo 'max(60,54)' | bc ~/MyExtensions.bc
60
Run Code Online (Sandbox Code Playgroud)
仅供参考,网上有免费的数学库函数,例如这个。
使用该文件,您可以轻松计算更复杂的函数,例如GCD
:
> echo 'gcd (60,54)' | bc ~/extensions.bc -l
6
Run Code Online (Sandbox Code Playgroud)
您可以将函数定义为
maxnum(){
if [ $2 -gt $1 ]
then
echo $2
else
echo $1
fi
}
Run Code Online (Sandbox Code Playgroud)
将其称为maxnum 54 42
,它会回响54
。如果您愿意,您可以在函数内添加验证信息(例如两个参数或数字作为参数)。