bash:ps grep for Umlaut(OS X)的进程

时间:2014-04-22 12:24:01

标签: macos bash shell grep

在shell脚本中

我需要找出特定应用程序是否仍在运行。如果我们的应用程序名称不包含任何 Umlauts (äöüàéè...),那么这将是一项简单的任务。我怎么能可靠地“grep”我的问题呢?

shell脚本在此示例中将应用程序名称作为参数“amétiqsiMedBüro.app”获取。有几个自定义副本同时运行,它们的名称不同,脚本应仅检查特定应用程序(通过param获取的应用程序)并忽略其他应用程序。

使用grep作为特定的app-name(param)时,

根本没有命中:

bash> ps ax | grep "amétiq siMed Büro.app"

bash>

点击次数过多:

bash> ps ax | grep "/[A]pplications/am" 
 4335   ??  S      5:19.01 /Applications/ame?M^Atiq siMed Bu?M^Hro.app/Contents/MacOS/siMed2
10188   ??  S      0:03.18 /Applications/ame?M^Atiq siMed SUPPORT.app/Contents/MacOS/siMed2

尝试手动缩小grep时再次没有命中:

bash> ps ax | grep "/[A]pplications/am" | grep "Büro"

bash>

似乎grep在第一次出现变音符号的位置后停止工作。

我也试过lsof - 没有成功。任何想法接下来要尝试什么?

运行OS X 10.7-10.9

3 个答案:

答案 0 :(得分:5)

TL;博士

  • 使用pgrep代替ps + grep
  • 使用iconv -t UTF8-MAC将搜索字符串转换为NFD(规范化分解的 Unicode)表单。
pgrep -qlf "$(iconv -t UTF8-MAC <<<'amétiq siMed Büro.app')" && echo "RUNNING"

简而言之: Mac文件系统(HFS +)以分解的 Unicode格式(NFD)存储文件名,而您在 shell中键入的内容 >是 编组 Unicode格式(NFC) shell和Unix实用程序都不会处理两个等效的字符串 - 同样的内容,不同的表单 - 作为内容相同的 - 即使他们应该

如果您对血腥细节感兴趣,请继续阅读。


背景

一些重音的Unicode字符有组合形式 - 直接表示字符的单个代码点(例如ü) - 以及等效 分解形式 - 基本字符后跟组合变音字符(例如u,后跟¨);有关详细信息,请参阅https://en.wikipedia.org/wiki/Unicode_equivalence

仅包含组合字符的字符串采用 NFC 普通[ized]格式( C 用于&#39; Composed&#39;),而字符串仅包含分解的是 NFD 正常[化]形式( D 用于&#39;分解&#39;)。

Mac文件系统(HFS +)将文件名存储在NFD(DEcomposed)中,具有以下含义:

  • 通过Finder和Spotlight 启动的应用程序在系统的流程表中表示为 NFD 字符串。
  • 同样,在 shell (在Terminal.app中使用bash)中,以下所有技术都会产生 NFD 字符串:

    • 路径名扩展(例如echo *.app
    • 来自ls和类似实用程序的输出
    • 提示时的交互式文件名完成
  • 相比之下,如果您在shell中键入脚本或应用程序名称(或从其他位置复制NFC表单),则会以 NFC 表示

问题的关键:shell和Unix实用程序无法识别NFD和NFC表单的等效性,因此将它们视为不同

- 繁琐且模糊不清 - 解决方法是仅将NFD字符串与NFD字符串匹配,并且仅针对NFC字符串匹配NFC字符串

阴险的是,给定字符串的NFD和NFC形式看起来在shell中完全相同 - 正如它们应该的那样 - 但处理的方式不同。< /强>

要确定给定字符串是否为NFD或NFC格式,请使用,例如:

 cat -v <<<'amétiq siMed Büro.app'
  • 如果字符串在NFC中,则输出与输入相同。
  • 如果字符串是NFD,如果输出包含乱码;例如,ame?M-^Atiq siMed Bu?M-^Hro.app(事实上,这是ps报告的内容 - 尽管不应该这样做。

或者,管道到hexdump -C以查看单个字节值。

请注意man关于ps未正确显示包含多字节字符的参数列表的注释本身并不正确(至少从OS X 10.9.2开始):NFC字符串是< / em>正确打印,而NFD则没有。 与pgrep对比,其中正确打印 NFD字符串,但在匹配时无法识别它们的等效性,如上所述。


在NFC和NFD形式之间转换

  • 在NFD和NFC之间一般转换任何字符串,请将iconvUTF8-MAC编码方案一起使用。

以下示例使用输入字符串'ü'

    NFC格式的
  • $'\xc3\xbc' - 即字节0xC3 0xBC,这是Unicode代码点0xFC
  • 的UTF8编码
  • 以NFD形式,$'u\xcc\x88' - 即u - base 字符 - 后跟字节0xCC 0x88,这是Unicode代码点的UTF8编码0x308,即所谓的组合分水岭(¨)。

证明转换;请注意,在终端中,结果将始终显示为ü - 管道到hexdump -C,例如,查看字节值。

  # NFC -> NFD
iconv -t UTF8-MAC <<<$'\xc3\xbc' # -> $'u\xcc\x88'

  # NFD -> NFC
iconv -f UTF8-MAC <<<$'u\xcc\x88' # -> $'\xc3\xbc'

这些转换可以安全使用,如果输入字符串已经是目标格式,则保持原样。

  • 要获得可重复使用的ANSI-C引用的字符串形式 - 无论是NFC还是NFD - 您可以使用下面列出的bash shell函数quoteNonAscii;在本案例中,以 NFD 形式表示应用程序名称:
    • cd/Applications(或您的申请所在的任何地方)
    • 运行quoteNonAscii am*tiq*siMed*B*ro.app - 路径名扩展将确保glob扩展为文件名的 NFD 形式。
# Pass any string to this function to output 
# an ANSI-C-quoted string with all non-ASCII bytes represented
# as \x{nn} hex. codes; trailing newlines are always trimmed.
# Examples:
#    quoteNonAscii 'ü'   # (if NFC) -> $'\xc3\xbc'
#    quoteNonAscii 'ü'  # (if NFD) -> $'u\xcc\x88'
quoteNonAscii() {
  hexdump -ve '/1 "%02x "' <<<"$*" | 
    awk -v RS=' '  '
      BEGIN { printf "$\x27" }                # print the opening of the ANSI-C-quoted string, `${single quote}`
      $1=="0a" { nls=nls "\x5cn"; next }      # store consecutive newlines in a temp. variable
      nls      { printf "%s", nls; nls="" }   # a non-newline char; we now know that the newlines stored so far are NOT trailing, so we print them and clear the temp. variable.
      $1>"7f"  { printf "\\x" $1; next }      # a non-ASCII byte -> PRINT AS `\xnn`
      $1=="22" { printf "\x5c\x22"; next }    # a double-quote char. -> escape with `\`
      $1=="27" { printf "\x5c\x27"; next }    # a single-quote char. -> escape with `\`
      $1=="07"  { printf "\\a"; next }        # bell char.
      $1=="08"  { printf "\\b"; next }        # backspace
      $1=="09"  { printf "\\t"; next }        # tab
      $1=="0b"  { printf "\\v"; next }        # vertical tab
      $1=="0c"  { printf "\\f"; next }        # ff
      $1=="0d"  { printf "\\r"; next }        # CR
      $1=="1b"  { printf "\\e"; next }        # escape
      { system("printf %b \"\\x" $1 "\"") }   # a byte that is an ASCII char -> print as a CHAR.
      END { print "\x27"}'                    # print the closing `{single quote}` of the ANSI-C-quoted string.  
}

macOS中的语言环境:

注意:这是原始答案的修订后的残余,希望仍然包含有用的信息。

  • 在交互式shell中运行locale会告诉您哪个区域设置生效,反映在以下环境变量中:LANGLC_COLLATELC_CTYPE,{{1} },LC_MESSAGESLC_MONETARYLC_NUMERIC。例如,如果美国英语区域设置生效,您可以看到:
LC_TIME
  • 默认情况下, LANG="en_US.UTF-8" LC_COLLATE="en_US.UTF-8" LC_CTYPE="en_US.UTF-8" LC_MESSAGES="en_US.UTF-8" LC_MONETARY="en_US.UTF-8" LC_NUMERIC="en_US.UTF-8" LC_TIME="en_US.UTF-8" LC_ALL= 和其他终端程序(例如Terminal.app)默认情况下预先配置shell的区域设置以匹配用户&#39; s通过iTerm 指定的区域设置(在System Preferences > Language & Region中,您可以通过Terminal.app关闭此行为,复选框Preferences... > Settings > {Your Profile} > Advanced)。

    • 字符编码 - 反映在区域设置ID的Set locale environment variables on startup后缀中,通常为.{encoding} - 将匹配终端程序中配置的编码&#39;设置(适用于.UTF8,转到Terminal.app并更改Preferences... > Settings > {Your Profile} > Advanced设置),支持(使用Character encoding查看所有支持的语言/区域+编码组合)。

    • locale -aTerminal 默认为UTF-8 ,这是明智的选择。

    • 如果您的终端程序配置为使用不支持的字符编码,则报告的区域设置ID将在{{1}中具有无编码后缀(例如,仅iTerm)并在en_US中完全恢复到Terminal语言环境 - 并且事情可能无法正常工作("C"仍会让你打印非ASCII字符编码,但实用程序不会将它们识别为字符,从而导致iTerm错误。

    • 同样,如果您在Terminal中配置不支持的主要语言和地理区域组合(例如,将#34;德语&#34;(illegal byte sequence)与&#34;美国&#34;(System Preferences),导致支持的区域设置de),只有US将与您的终端程序的编码匹配,并且其他de_US类别将默认为LC_TYPE
  • 如果您需要手动设置区域设置 ,请运行:

    • LC_*
    • "C"

    区别在于export LANG={localeId}为所有export LC_ALL={localeId}类别提供默认,同时允许您有选择地覆盖它们,而export LANG=... 覆盖所有LC_*类别。
    支持的区域设置ID可以与export LC_ALL=...一起列出;最好选择一个基于UTF-8的,例如LC_* 可以通过locale -ade_CH.UTF-8选择POSIX locale - 实质上是仅限ASCII的美国英语区域设置。

  • 随着macOS附带的
  • 警告所有Unix实用程序会遇到上述问题:他们无法识别NFC和NFD中的等效Unicode字符串相同。 除了这个问题,许多,但并非所有Unix实用程序原则上都是UTF8多字节字符识别

    • 从macOS 10.14开始的值得注意的例外 - 即不是UTF8的 的实用程序 - 是 { {1}} ;在早期的macOS版本"POSIX"中也没有UTF8感知(当先前使用的过时GNU实现被最近的BSD实现替换时,这种情况发生了变化)。

答案 1 :(得分:0)

您必须设置区域设置以匹配重音,例如:

$ export LC_ALL="en_US.UTF-8"
$ echo "amétiq siMed Büro.app" | grep ü

结果

$ export LC_ALL="en_US"                                                                      
$ echo "amétiq siMed Büro.app" | grep ü
amétiq siMed Büro.app

ps示例:

$ export LC_ALL="en_US"
$ tail -f ü.k &
[1] 57945
$ ps -ef | grep ü[.]
klashxx   57945 27535  0 15:02 pts/6    00:00:00 tail -f ü.k

答案 2 :(得分:0)

似乎我用osascript / AppleScript来解决我的问题太快了 - 我能够在终端中过滤我的问题,但由于某些原因它在我的脚本中无效...

所以这里是我发现解决问题的方法:如果我不能使用ps,lsof等命令可靠地“grep”应用程序路径...匹配我的脚本获取为param的路径,那么我只需要重新在新流程的帮助下生成它。

再次,我的问题简而言之:

我的脚本获取应用程序路径作为参数。这条路径包含变音符号。此外,应用程序有几个变体,命名方式不同,其中几个可能同时运行,但脚本需要精确过滤它作为参数获取的那个。

/Applications/amétiq siMed Büro.app/Contents/MacOS/siMed2

使用ps,lsof等。我得到乱码输出,无论我设置了什么语言环境,它都没有匹配我的参数:

bash> ps ax | grep "/[A]pplications/am"
70202   ??  S      1:56.38 /Applications/ame?M^Atiq siMed Bu?M^Hro.app/Contents/MacOS/siMed2
75164   ??  U      0:01.75 /Applications/ame?M^Atiq siMed MASTER SN.app/Contents/MacOS/siMed2
只要字符串中包含变音符号,

grep就会失败:

bash> ps ax | grep "/[A]pplications/amétiq siMed Büro.app"
(empty result)

我的解决方案是开始“尾巴&amp;”处理应用程序包中存在的文件,然后执行一些ps,cut和awk,以获取我正在寻找的应用程序的pid:

cd "/Applications/amétiq siMed Büro.app"  # path the script gets as param
tail -f ./Contents/MacOS/helperfile.txt &
helperpid=$!  # pid of tail process
gr="`lsof -p $helperpid | cut -d'/' -f 2- | grep '/Contents/MacOS/' | sed 's:/Contents/MacOS.*$::' | head -1`"
kill $helperpid  # helper process no longer needed
finalpid=`lsof | grep "$gr" | grep "app/Contents/MacOS" | awk '{print $2}'`
# $finalpid contains the pid of the process in question

请注意,我必须将LC_ALL和LANG设置为“en_US.UTF-8”(可能不需要设置其中一个,我没有进一步深入研究......)。

我知道这只是一种解决方法,拥有一个oneliner会更好......至少这个解决方案对我有用。再次感谢参与讨论这个问题的任何人!