跳转到主要内容

是否曾希望能在Bash中使用Python语法?那么就让它变得像Python一样,通过`pz`实用程序将内容通过你的小型Python脚本进行管道处理!

项目描述

pz

Build Status Downloads

是否曾希望能在Bash中使用Python?你会选择Python语法而不是sedawk...?你应该确切知道在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,则无论怎样都会输出行。

iSLDC – 其他全局变量

一些变量已初始化并准备好在全局范围内使用。它们适用于所有行。

  • 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
    

命令子句

  • COMMANDmain子句,在每一行上执行的任何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 防止 linesnumberstext 变量可用。在处理无限输入时很有用。

    # 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 完成功能

  1. 运行:apt-get install bash-completion jq
  2. 复制:extra/pz-autocompletion.bash/etc/bash_completion.d/
  3. 重启终端

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。

源分发

pz-1.1.0.tar.gz (39.2 kB 查看哈希)

上传时间:

构建分发

pz-1.1.0-py3-none-any.whl (30.6 kB 查看哈希)

上传于 Python 3

支持