ZSH 函数中字符串长度始终返回 2

ZSH 函数中字符串长度始终返回 2

我试图通过生成与输入长度相同的随机字符串来对固定长度文本文件中的电子邮件地址进行鲍德勒化。我将字符串作为 sed 中的反向引用传递。

为了简化,这个脚本(临时):

#!/bin/zsh
IFS=$'\n'       # make newlines the only separator
set -f          # disable globbing

#show me the input from the command line
echo $1 ${#1}

function randString() {
    # just echo for demonstration
    echo $1 ${#1}
    # this is the bit I really want:
    # cat /dev/urandom | LC_ALL=C tr -cd "[a-z]" | head -c${#1}
}

for line in $(cat $1); do
    echo $line |
        sed "s/\([a-zA-Z0-9]\{2,\}\)@\([a-zA-Z0-9]\{2,\}\)\.\([a-zA-Z0-9]\{2,\}\)/$(randString \\1)@$(randString \\2).$(randString \\3)/"

done

使用此数据(temp.txt):

me [email protected]
you [email protected]

像这样运行:

./temp temp.txt

给我这个输出:

temp.txt 8
me myemail 2@someserver 2.com 2
you youremail 2@anotherserver 2.biz 2

问题是,${#1}无论我输入什么字符串,它都会返回 2。正确的字符串如何返回,$1而长度却${#1}如此错误?IFS文件循环的设置是否会终止我的功能?

注意:我使用的是 Mac,所以没有 GNU 扩展。

答案1

一些诊断位显示正在发生的情况。

将这一行echo rand $1 ${#1} >&2添加到randString函数后,输出如下:

temp.txt 8
rand \1 2
rand \2 2
rand \3 2
me myemail 2@someserver 2.com 2
rand \1 2
rand \2 2
rand \3 2
you youremail 2@anotherserver 2.biz 2

stderr通过回显with的输入>&2,我们可以看到randString正在使用参数\1, \2and \3(长度为 2)调用它,而不是那些反向引用应该指示的字符串。

下一个测试是在调用前加上sedwith ,echo这样我们就可以看到它得到了什么参数。其输出:

sed s/\([a-zA-Z0-9]\{2,\}\)@\([a-zA-Z0-9]\{2,\}\)\.\([a-zA-Z0-9]\{2,\}\)/\1 2@\2 2.\3 2/

这样,sed就被告知将字符串替换为类似 的字符串\1 2,即对输入字符串的反向引用,后跟一个空格和数字 2。输入电子邮件地址中的字符串来自sed,而不是来自echo函数中的 。

这是因为字符串中的命令替换(扩展$(...))是在字符串作为参数zsh传递之前处理的。sed为了将输入字符串传递给函数,您需要sed调用 shell 函数。但可能没有办法做到这一点默认版本sed


编辑添加:一个用于修改电子邮件地址的快速脚本,主要是zsh

#!/usr/bin/env zsh
setopt extendedglob

coproc cat /dev/urandom | LC_ALL=C tr -cd "[:lower:]" 

getRand (){
  print -r -- ${1//(#m)[[:alnum:]]/$(read -psk var;echo $var)}
}

while read line; do
  print -r -- ${line/(#m)[[:alnum:]](#c2,)@[[:alnum:]](#c2,).[[:alnum:]](#c2,)/$(getRand ${MATCH})}
done < ${1:?}

答案2

#!/bin/zsh
IFS=$'\n'       # make newlines the only separator
set -f          # disable globbing

zsh -f,就像csh -f是跳过读取启动文件,而不是禁用通配符(除非在 sh/ksh 模拟中),为此您需要set -o noglobset +o glob(或带有setopt/ 的变体unsetopt)。

您可以set -f在其他类似 Bourne 的 shell 中使用它来解决其错误功能,即在未加引号的扩展上执行通配符。但 zsh 没有这个缺陷,因为globsubst默认情况下该选项是禁用的(当不在sh/ksh模拟模式下时)。

#show me the input from the command line
echo $1 ${#1}

它应该是print -r -- $1 $#1orecho -E - $1 $#1或or ,对于包含s 的值或某些以 开头的值printf '%s\n' "$1 $#1"将无法正常工作。$1\-

function randString() {

我会在randString() ...Bourne 语法或function randString {Korn 语法之间进行选择,但不会使用该组合(但这只是一个品味问题)。

    # cat /dev/urandom | LC_ALL=C tr -cd "[a-z]" | head -c${#1}

连接cat单个文件没有什么意义。

请注意,在大多数tr实现中,tr -cd "[a-z]"还会产生[]字符。

$ echo '[]123ab' | tr -cd '[a-z]'
[]ab
$ echo '[]123ab' | tr -cd a-z
ab
}

for line in $(cat $1); do

这不是在 shell 中处理文本的方式。看为什么使用 shell 循环处理文本被认为是不好的做法?

    echo $line |
        sed "s/\([a-zA-Z0-9]\{2,\}\)@\([a-zA-Z0-9]\{2,\}\)\.\([a-zA-Z0-9]\{2,\}\)/$(randString \\1)@$(randString \\2).$(randString \\3)/"

在那里,shell 将$(...)在调用之前执行扩展sed。使用文字作为参数randString \\1进行调用,因此最终会使用参数进行调用。randString\1seds/\([a-zA-Z0-9]\{2,\}\)@\([a-zA-Z0-9]\{2,\}\)\.\([a-zA-Z0-9]\{2,\}\)/\12@\22.\32/

还要注意什么[a-z]和co。匹配取决于区域设置。

在这里,您应该运行一次文本处理实用程序的调用,最好是可以生成随机字符串的调用。就像是:

#! /bin/sh -
exec perl -Tpe 's{\w{2,}@\w{2,}\.\w{2,}}{
  $& =~ s/\w/chr 96 + rand(26)/ger}ge' -- "$@"

这里使用sh而不是zshassh就足够了,并调用perl以更简单和更有效的方式进行文本处理并生成随机字符串。

或者编写一个perl脚本来代替:

#! /usr/bin/perl --

while (<<>>) {
  s{\w{2,}@\w{2,}\.\w{2,}}{$& =~ s/\w/chr 96 + rand(26)/ger}ge;
  print;
}

这里使用<<>>而不是<>.该-p选项意味着一个<>循环,它允许传递诸如ls|处理 的输出之类的东西ls,而不是名为 的文件ls|,但那就是相当危险。使用该选项可以在一定程度上缓解并解决-T安全问题。<<>>

在内部做类似的事情zsh是可能的,但不会很漂亮。

#! /bin/zsh -
zmodload zsh/mathfunc
zmodload zsh/mapfile
set -o extendedglob

pattern='[[:alnum:]](#c2,)@[[:alnum:]](#c2,).[[:alnum:]](#c2,)'
for file do
  print -rn -- ${mapfile[$file]//(#m)$~pattern/${MATCH//(#m)[[:alnum:]]/${(L)$(( [##36] rand48() * 26 + 9))}}}
done

这里使用:

  • $mapfile[file]加载文件的内容
  • zsh 自己的 glob 运算符代替正则表达式进行匹配
  • rand48()生成随机数,这里以 36 为基数,在 10 到 35 之间,将字母输出AZ,并使用参数扩展标志转换为小写L

相关内容