是否曾希望能在Bash中使用Python语法?那么就让它变得像Python一样,通过`pz`实用程序将内容通过你的小型Python脚本进行管道处理!
项目描述
pz
是否曾希望能在Bash中使用Python?你会选择Python语法而不是sed
、awk
...?你应该确切知道在Python中会使用什么命令,但最后却不得不一次又一次地查询man
,请继续阅读。该实用程序允许你将shellpython化:通过加载你的小型Python脚本,通过pz
将任意内容进行管道处理。
如何?只需简单修改s
变量。例如:将'.com'追加到每一行。
$ echo -e "example\nwikipedia" | pz 's += ".com"'
example.com
wikipedia.com
安装
从 PyPi 使用单个命令安装。
pip3 install pz
或从这里下载并运行 pz
文件。
示例
使用 pz
处理数据后看起来如何?该工具可以替换哪些 Bash 程序?
提取子字符串
只需使用 [:]
表示法。
echo "hello world" | pz s[6:] # world
请注意,抑制参数周围的引号可能不起作用(Zsh)或导致意外行为:touch s1 && echo "hello" | pz s[1]
→ 异常:<class 'NameError'>
。请使用 echo "hello" | pz 's[1]'
代替。
在流中的每一行前添加内容
我们在行前面添加行长度。
# let's use the f-string `--format` flag
tail -f /var/log/syslog | pz -f '{len(s)}: {s}'
# or do it the long way, explicitly setting the `s` variable
tail -f /var/log/syslog | pz 's = str(len(s)) + ": " + s'
转换为大写
替换 | tr '[:upper:]' '[:lower:]'
。
echo "HELLO" | pz s.lower # "hello"
反转行
替换 | tac
或 | tail -r
(在某些系统上仅限)或 | sed '1!G;h;$!d'
(仅限酷炫人士)。
$ echo -e "1\n2\n3" | pz -E 'lines[::-1]'
3
2
1
解析数字
替换 cut
。请注意,您可以链式调用多个 pz
调用。按逗号 ',
' 分割,然后使用 n
访问转换为数字的行。
echo "hello,5" | pz 's.split(",")[1]' | pz n+7 # 12
在文本中查找所有URL
替换 sed
。我们知道所有来自 re
库的函数都已包含在内,例如:"findall"。
# either use the `--findall` flag
pz --findall "(https?://[^\s]+)" < file.log
# or expand the full command to which is the `--findall` flag equivalent
pz "findall(r'(https?://[^\s]+)', s)" < file.log
如果链式调用,您可以在当前网络浏览器中打开所有 URL。请注意,函数 webbrowser.open
从标准库自动导入。
pz --findall "(https?://[^\s]+)" < file.log | pz webbrowser.open
求和数字
替换 | awk '{count+=$1} END{print count}'
或 | paste -sd+ | bc
。只需在 --end
子句中使用 sum
。
# internally changed to --end `s = sum(numbers)`
echo -e "1\n2\n3\n4" | pz --end sum # 10
保留唯一行
替换 | sort | uniq
没有意义,但演示给您提供了想法。我们初始化一个集合 c
(类似于 集合)。在处理一行时,如果已经看到,则将 skip
设置为 True
。
$ echo -e "1\n2\n2\n3" | pz "skip = s in c; c.add(s)" --setup "c=set()"
1
2
3
然而,当处理流时,与 | sort | uniq
相比,有一个优点。您可以看到唯一的行,而无需等待流完成。与 tail --follow
结合使用时很有用。
作为替代方案,为了确保值已排序,我们可以使用 --end
标志,在处理完成后产生输出。
echo -e "1\n2\n2\n3" | pz "S.add(s)" --end "sorted(S)" -0
请注意,我们使用了变量 S
,默认情况下初始化为空集(因此我们根本不需要使用 --setup
),并使用标志 -0
防止处理输出(因此我们不需要使用 skip
参数)。
(严格来说,我们可以省略 -0
。如果您使用详细 -v
标志,您会在内部看到命令更改为 s = S.add(s)
。由于 set.add
产生 None
输出,因此它等同于跳过。)
我们可以在 main
子句中省略 (s)
,从而消除所有引号。
echo -e "1\n2\n2\n3" | pz S.add --end "sorted(S)"
尽管如此,最直接的方法涉及可用的 lines
变量,当使用 --end
子句时。
echo -e "1\n2\n2\n3" | pz --end "sorted(set(lines))"
计算单词数
我们将行分割成单词,并将它们放入 S
,这是一个全局的 set
实例。然后,我们打印集合长度以获取唯一单词的数量。
echo -e "red green\nblue red green" | pz 'S.update(s.split())' --end 'len(S)' # 3
但如果我们想获取最常见的单词及其使用次数呢?让我们使用全局实例的 collections.Counter
,即C
。我们可以看到red
是最常用的单词,使用了2次。
$ echo -e "red green\nblue red green" | pz 'C.update(s.split())' --end C.most_common
red 2
green 2
blue 1
聚合目录中的后缀
为了快速了解路径上存在多少个文件扩展名,首先将文件名转换为后缀。然后,将它们传递给collections.Counter
构造函数。
$ ls
a.txt b.txt c.txt v1.mp4 v2.mp4
$ ls | pz 'Path(s).suffix' | pz --end 'Counter(lines).most_common'
.txt 3
.mp4 2
获取网页内容
得益于requests
库,访问互联网变得很简单。[链接](https://requests.pythonlang.cn/en/master/)。在这里,我们获取example.com
,使用grep搜索所有包含“href”的行,并打印它们,同时去除空格。
$ echo "http://example.com" | pz 'requests.get(s).content' | grep href | pz s.strip
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
要查看自动导入是如何解决的,请使用详细模式。(注意Importing requests
这一行。)
$ echo "http://example.com" | pz 'requests.get(s).content' -v | grep href | pz s.strip
Changing the command clause to: s = requests.get(s).content
Importing requests
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
处理嵌套引号
为了匹配每行包含引号表达式并打印出引号内容,你可以使用Python的三重引号。在下面的示例中,使用撇号来界定COMMAND
标志。如果我们使用文本中的撇号,我们则需要转义它。相反,三重引号可能可以提高可读性。
echo -e 'hello "world".' | pz 'match(r"""[^"]*"(.*)".""", s)' # world
在这种情况下,更好的方法是使用--match
标志尽可能去除引号。
echo -e 'hello "world".' | pz --match '[^"]*"(.*)"' # world
计算阶乘
看看多种方法。最简单的是使用函数。
echo 5 | pz factorial # 120
在后台发生了什么?factorial
来自math.factorial
。由于它是一个可调用对象,我们尝试将当前行作为参数:factorial(s)
。由于s = "5"
表示一个字符串,它失败了。然后它尝试使用factorial(n)
,其中n
是自动从当前行转换成数字。这成功了。
更难的方法?让我们使用math.prod
。
echo 5 | pz 'prod(i for i in range(1,n+1))' # 120
不使用任何内置库?让我们只使用一个for循环。处理从1到n
(即5)的所有数字并将它们相乘得到乘积。最后,将n
赋值给输出变量s
。
echo 5 | pz 'for c in range(1,n): n*= c ; s = n' # 120
使用生成器将打印从1到-g
的每个数字的阶乘。
$ pz factorial -g5
1
2
6
24
120
读取CSV
由于csv
是自动导入的库之一,我们可以直接访问并实例化读取器对象。在下面的示例中,我们输出每行的第二个元素,要么逐行输出,要么在处理完成后一次性输出。
# output line by line
echo '"a","b1,b2,b3","c"' | pz "(x[1] for x in csv.reader([s]))" # "b1,b2,b3"
# output at the end
echo '"a","b1,b2,b3","c"' | pz --end "(x[1] for x in csv.reader(lines))" # "b1,b2,b3"
生成随机数
首先,看看如何在Bash中流式传输随机数到100。
while :; do echo $((1+$RANDOM%100)); done
现在检查没有涉及pz
的纯Python解决方案。
python3 -c "while True: from random import randint; print(randint(1,100))"
使用pz
,我们减轻了命令的循环处理和导入负担。
pz "randint(1,100)" --generate=0
让我们生成一些长度为1到30的可变随机字符串。当使用生成器标志而不指定数字时,它会循环五次。
pz "''.join(random.choice(string.ascii_letters) for _ in range(randint(1,30)))" -S "import string" -g
计算流值的平均值
让我们输出流并输出平均值。
# print out current line `count` and current average `sum/count`
$ while :; do echo $((1 + $RANDOM % 100)) ; sleep 0.1; done | pz 'sum+=n;s=count, sum/count' --setup "sum=0"
1 38.0
2 67.0
3 62.0
4 49.75
# print out every 10 000 lines
# (thanks to `not i % 10000` expression)
$ while :; do echo $((1 + $RANDOM % 100)) ; done | pz 'sum+=n;s=sum/count; s = (count,s) if not count % 10000 else ""' --setup "sum=0"
10000 50.9058
20000 50.7344
30000 50.693466666666666
40000 50.5904
这可以简化吗?让我们使用无限生成器-g0
。正如我们所知,生成器给当前行提供n
,默认情况下隐式声明为i=0
,所以我们用它来保存总和。不需要设置子句。不需要Bash循环。
$ pz "i+=randint(1,100); s = (n,i/n) if not n % 10000 else ''" -g0
10000 49.9488
20000 50.5399
30000 50.39906666666667
40000 50.494425
多行语句
如果您需要评估一个短的多行语句,请使用Bash支持的常规多行语句。
$ echo -e "1\n2\n3" | pz "if n > 2:
s = 'bigger'
else:
s = 'smaller'
"
smaller
bigger
bigger
简单的进度条
通过生成一个长序列的数字来模拟长时间的处理(由于它们不需要,我们通过1>/dev/null
将它们丢弃)。在每100行,我们将光标向上移动(\033[1A
),清除行(\033[K
)并打印到STDERR
当前状态。
$ seq 1 100000 | pz 's = f"\033[1A\033[K ... {count} ..." if count % 100 == 0 else None ' --stderr 1>/dev/null
... 100 ... # replaced by ... 200 ...
文档
作用域变量
在脚本范围内,您可以访问以下变量
s
– 当前行
根据您的需求进行更改
echo 5 | pz 's += "4"' # 54
n
– 将当前行转换为 int
(或 float
)
echo 5 | pz n+2 # 7
echo 5.2 | pz n+2 # 7.2
b
– 当前行作为字节字符串
有时输入不能轻松转换为str。虽然会输出警告,但您仍然可以使用原始字节进行操作。
echo -e '\x80 invalid line' | pz s
Cannot parse line correctly: b'\x80 invalid line'
� invalid line
# use the `--quiet` flag to suppress the warning, then decode the bytes
echo -e '\x80 invalid line' | pz 'b.decode("cp1250")' --quiet
€ invalid line
count
– 当前行号
# display every 1_000nth line
$ pz -g0 n*3 | pz "n if not count % 1000 else None"
3000
6000
9000
# the same, using the `--filter` flag
$ pz -g0 n*3 | pz -F "not count % 1000"
text
– 整个文本,所有行合并
设置--overflow-safe
标志时不可用,除非在main
子句中设置了--whole
标志。例如,获取字符数(| wc -c
的替代方法)。
echo -e "hello\nworld" | pz --end 'len(text)' # 11
当在main
子句中使用时,会显示错误。
$ echo -e "1\n2\n3" | pz 'len(text)'
Did not you forget to use --text?
Exception: <class 'NameError'> name 'text' is not defined on line: 1
附加--whole
有助于,但结果会逐行处理。
$ echo -e "1\n2\n3" | pz 'len(text)' -w
5
5
5
附加-1
确保该语句只计算一次。
$ echo -e "1\n2\n3" | pz 'len(text)' -w1
5
lines
– 已处理的行列表
设置--overflow-safe
标志时不可用。
例如,返回最后一行
echo -e "hello\nworld" | pz --end lines[-1] # "world"
numbers
– 已处理的数字列表
设置--overflow-safe
标志时不可用。
示例:显示流当前的平均值。更具体地说,我们输出元组:行数,当前行,平均值
。
$ echo -e "20\n40\n25\n28" | pz 's = count, s, sum(numbers)/count'
1 20 20.0
2 40 30.0
3 25 28.333333333333332
4 28 28.25
skip
跳过行
如果设置为True
,则不会输出当前行。如果在使用-0
标志时设置为False
,则无论怎样都会输出行。
i
、S
、L
、D
、C
– 其他全局变量
一些变量已初始化并准备好在全局范围内使用。它们适用于所有行。
i = 0
S = set()
L = list()
D = dict()
C = Counter()
确实,使用大写字母并不符合命名规范。然而,在这些小脚本中,可读性是最重要的原则,每个字符都很重要。
使用集合S
。在示例中,我们将每一行添加到集合中,并以排序的方式打印出来。
$ echo -e "2\n1\n2\n3\n1" | pz "S.add(s)" --end "sorted(S)"
1
2
3
使用列表L
。将包含一个大于一的数字的行添加到列表中,最后打印它们的计数。由于只关心最终计数,因此使用标志-0
抑制行输出。
$ echo -e "2\n1\n2\n3\n1" | pz "if n > 1: L.append(s)" --end "len(L)" -0
3
自动导入
- 您可以始终手动导入所需的库。(将
import
语句放入命令。) - 一些库已准备好使用:
re.*(匹配、搜索、findall),math.*(sqrt,...),defaultdict
- 其他库在使用时自动导入。在这种情况下,将重新处理该行。
- 函数:
b64decode,b64encode,datetime,(requests).get,glob,iglob,Path,randint,sleep,time,ZipFile
- 模块:
base64,collections,csv,humanize,itertools,jsonpickle,pathlib,random,requests,time,webbrowser,zipfile
- 函数:
注意:第一次访问时,自动导入将导致行重新处理。这可能影响您的全局变量。使用详细输出以查看是否已自动导入。
$ echo -e "hey\nbuddy" | pz 'a+=1; sleep(1); b+=1; s = a,b ' --setup "a=0;b=0;" -v
Importing sleep from time
2 1
3 2
正如您所看到的,a
被增加了3次,而b
增加了两次,因为我们必须处理第一行两次以自动导入sleep。在第一次运行中,处理引发了一个异常,因为sleep
是未知的。为了避免这种情况,可以将from time import sleep
显式地附加到--setup
标志上。
输出
-
显式赋值:默认情况下,我们输出
s
。echo "5" | pz 's = len(s)' # 1
-
单个表达式:如果没有显式设置,我们将自动将表达式赋给
s
。echo "5" | pz 'len(s)' # 1 (command internally changed to `s = len(s)`)
-
元组、生成器:如果
s
最终成为元组,它将按制表符连接。$ echo "5" | pz 's, len(s)' 5 1
考虑管道两个行'hey'和'buddy'。我们返回三个元素,原始文本,反转文本及其长度。
$ echo -e "hey\nbuddy" | pz 's,s[::-1],len(s)' hey yeh 3 buddy yddub 5
-
列表:如果
s
最终成为列表,则其元素将单独打印到不同的行。$ echo "5" | pz '[s, len(s)]' 5 1
-
正则匹配:所有组都视为元组。如果没有使用组,则打印整个匹配的字符串。
# no group → print entire matched string echo "hello world" | pz 'search(r"\s.*", s)' # " world" # single matched group echo "hello world" | pz 'search(r"\s(.*)", s)' # "world" # matched groups treated as tuple echo "hello world" | pz 'search(r"(.*)\s(.*)", s)' # "hello world"
-
可调用:它会调用。在处理简单的函数时非常有用 - 无需显式放置括号来调用函数,我们可以在Bash(表达式
s.lower()
将需要引号)中省略引号。使用详细标志-v
来检查命令的内部变化。# internally changed to `s = s.lower()` echo "HEllO" | pz s.lower # "hello" # internally changed to `s = len(s)` echo "HEllO" | pz len # "5" # internally changed to `s = base64.b64encode(s.encode('utf-8'))` echo "HEllO" | pz b64encode # "SEVsbE8=" # internally changed to `s = math.sqrt(n)` # and then to `s = round(n)` echo "25" | pz sqrt | pz round # "5" # internally changed to `s = sum(numbers)` echo -e "1\n2\n3\n4" | pz sum 1 3 6 10 # internally changed to `' - '.join(lines)` echo -e "1\n2\n3\n4" | pz --end "' - '.join" 1 - 2 - 3 - 4
如您在示例中看到的,如果引发
TypeError
,我们尝试在添加当前行作为参数的同时重新处理该行- 其基本形式
s
- 如果有的话,
numbers
- 如果有的话,使用其数字表示形式
n
- 编码为字节
s.encode('utf-8')
在
--end
子句中,我们还尝试使用lines
。 - 其基本形式
CLI 标志
-v
,--verbose
:查看底层发生了什么。显示自动导入和内部命令修改(尝试使其可调用,并在省略时添加s =
)。$ echo -e "hello" | pz 'invalid command' Exception: <class 'SyntaxError'> invalid syntax (<string>, line 1) on line: hello $ echo -e "hello" | pz 'sleep(1)' --verbose Importing sleep from time
-q
,--quiet
:仅显示错误和值。抑制命令异常。echo -e "hello" | pz 'invalid command' --quiet # empty result
命令子句
COMMAND
:main
子句,在每一行上执行的任何Python脚本(允许多个语句)-E COMMAND
,--end COMMAND
:任何Python脚本,在处理之后执行。对于最终输出非常有用。默认情况下,变量text
在此处可用。$ echo -e "1\n2\n3\n4" | pz --end sum 10 $ echo -e "1\n2\n3\n4" | pz s --end sum 1 # output of the `main` clause 2 3 4 10 # output of the `end` clause $ echo -e "1\n2\n3\n4" | pz sum --end sum 1 # output of the `main` clause 3 6 10 10 # output of the `end` clause
-S COMMAND
,--setup COMMAND
:在处理之前执行的任何 Python 脚本。用于变量初始化。例如:通过增加变量count
来添加行号。$ echo -e "row\nanother row" | pz 'count+=1;s = f"{count}: {s}"' --setup 'count=0' 1: row 2: another row # the same using globally available variable `count` instead of using `--setup` and the `--format` flag $ echo -e "row\nanother row" | pz -f '{count}: {s}'
-I
,--insecure
:如果设置,环境变量PZ_SETUP
中的任何 Python 脚本将在--setup
子句之前执行。用于导入。由于用户可能会在攻击者篡改变量的情况下运行不期望的代码,我们暂时通过此标志对其进行评估。$ echo -e "1\n2\n3" | PZ_SETUP='from hashlib import sha3_256' pz -I 'sha3_256(b).hexdigest' # equivalent to: $ echo -e "1\n2\n3" | pz --setup 'from hashlib import sha3_256' 'sha3_256(b).hexdigest' 67b176705b46206614219f47a05aee7ae6a3edbe850bbbe214c536b989aea4d2 b1b1bd1ed240b1496c81ccf19ceccf2af6fd24fac10ae42023628abbe2687310 1bf0b26eb2090599dd68cbb42c86a674cb07ab7adc103ad3ccdf521bb79056b9
-F
,--filter
:行被管道输出不变,但只有当评估为True
时。当将数字管道输入 5 时,我们只传递大于 3 的数字。$ echo -e "1\n2\n3\n4\n5" | pz "n > 3" --filter 4 5
此语句与使用skip
(而不是使用--filter
)等效。$ echo -e "1\n2\n3\n4\n5" | pz "skip = not n > 3" 4 5
当不使用过滤器时,s
评估为True
/False
。默认情况下,不输出False
或空值。$ echo -e "1\n2\n3\n4\n5" | pz "n > 3" True True
-f
,--format
:主要和结束子句被视为 f 字符串。子句内部插入到三个撇号f'''COMMAND'''
之间。
输入/输出
-
-n NUM
仅处理指定行数的行。大致等同于head -n
。 -
-1
仅处理第一行。 -
-0
跳过所有输出行。(与--end
结合使用很有用。) -
--empty
输出空行。(默认情况下跳过。)
考虑缩短文本的最后三个字母。然后第一行hey
完全消失。$ echo -e "hey\nbuddy" | pz 's[:-3]' bu
如果我们坚持显示,现在会看到一个空行。
$ echo -e "hey\nbuddy" | pz 's[:-3]' --empty bu
-
-g [NUM]
,--generate [NUM]
忽略输入管道生成行。行将与迭代周期计数相对应(除非在具有无限生成器的--overflow-safe
标志的情况下 - 在这种情况下,行将等于 '1')。如果没有指定NUM
,则默认生成 5 行。将NUM == 0
表示为无限生成器。如果没有设置main
子句,则输出该数字。$ pz -g2 1 2 $ pz 'i=i+5' -g -v Changing the main clause to: s = i=i+5 Generating s = 1 .. 5 5 10 15 20 25
-
--stderr
将子句的输出打印到STDERR
,同时让原始行完整地管道到STDOUT
。在长时间操作期间生成报告很有用。查看以下示例,每行中的第三行将使STDERR
接收一条消息。$ pz -g=9 s | pz "s = 'Processed next few lines' if count % 3 == 0 else None" --stderr 1 2 3 Processed next few lines 4 5 6 Processed next few lines 7 8 9 Processed next few lines
通过将
STDOUT
写入文件并让STDERR
留在终端中来演示不同的管道。$ pz -g=9 s | pz "s = 'Processed next few lines' if count % 3 == 0 else None" --stderr > /tmp/example Processed next few lines Processed next few lines Processed next few lines cat /tmp/example 1 2 3 ...
-
--overflow-safe
防止lines
、numbers
、text
变量可用。在处理无限输入时很有用。# prevent `text` to be populated by default echo -e "1\n2\n2\n3" | pz --end "len(text)" --overflow-safe Did you not forget to use --whole to access `text`? Exception: <class 'NameError'> name 'text' is not defined in the --end clause # force to populate `text` echo -e "1\n2\n2\n3" | pz --end "len(text)" --overflow-safe --whole 7
正则表达式快捷方式
-
--search
等效于search(COMMAND, s)
$ echo -e "hello world\nanother words" | pz --search ".*\s" hello another
-
--match
等效于match(COMMAND, s)
-
--findall
等效于findall(COMMAND, s)
-
--sub SUBSTITUTION
等效于sub(COMMAND, SUBSTITUTION, s)
$ echo -e "hello world\nanother words" | pz ".*\s" --sub ":" :world :words
使用组
$ echo -e "hello world\nanother words" | pz "(.*)\s" --sub "\1" helloworld anotherwords
Bash 完成功能
- 运行:
apt-get install bash-completion jq
- 复制:extra/pz-autocompletion.bash 到
/etc/bash_completion.d/
- 重启终端
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。