shell bash:ps grep用于使用Umlaut(OS X)的进程

qnyhuwrf  于 2023-05-18  发布在  Shell
关注(0)|答案(3)|浏览(146)

在shell脚本中,我需要找出一个特定的应用程序是否仍在运行。如果我们的应用程序名称不包含任何Umluts(äöüàéè...),这将是一个简单的任务。我怎样才能可靠地为我的进程“grep”?
shell脚本获取应用程序名称作为参数,在本例中为“amétiq siMed Büro.app”。有几个自定义的副本同时运行,它们的命名不同,脚本应该只检查一个特定的应用程序(通过param获取的应用程序),忽略其他应用程序。
当使用grep作为特定应用程序名称时,完全没有命中(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在Umlaut字符第一次出现的位置后停止工作。
我也试过lsof-没有成功。知道下一步该做什么吗
运行OS X 10.7-10.9

iyfamqjs

iyfamqjs1#

tl; dr

  • 使用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的文件名是*composed * Unicode form(NFC)**,**shell和Unix实用程序都不会将两个 * 等价的 * 字符串-相同的 * content 、不同的 * form -视为 * content-equivalent,即使它们 * 应该 *。
如果你对血腥的细节感兴趣,请继续阅读。

背景

一些重音Unicode字符有一个组成形式-一个代码点直接表示字符(例如:ü)-以及 * 等价分解形式-基字符后跟一个组合变音字符(例如,u,后跟¨);请参阅https://en.wikipedia.org/wiki/Unicode_equivalence了解更多信息。
仅包含合成字符的字符串为NFC正常[ized]形式(
C * 表示“Composed”),而仅包含分解字符的字符串为NFD正常[ized]形式(* D * 表示“Decomposed”)。
Mac文件系统(HFS+)将文件名存储在NFD(DEcomposed)中,这有以下含义:

      • 通过Finder和Spotlight启动的应用程序在系统进程表中以NFD**字符串表示。
  • 类似地,在shell(www.example.com中的bash)中,以下所有技术都会产生NFD字符串:Terminal.app), all of the following techniques yieldNFDstrings:
  • 路径名扩展(例如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字符串 * 被正确打印,而NFD字符串则不是。与pgrep形成对比,pgrep可以正确打印NFC * 和 * NFD字符串,但在 * 匹配 * 时无法识别它们的等价性,如上所述。

NFC和NFD表单转换

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

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

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

演示转换;注意,在终端中,结果将始终显示为ü-pipe to 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-* pathname expansion * 将确保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_CTYPELC_MESSAGESLC_MONETARYLC_NUMERICLC_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和其他终端程序,如iTerm默认情况下预先配置shell的区域设置,以匹配通过System Preferences > Language & Region指定的用户区域设置(在Terminal.app中,您可以通过Preferences... > Settings > {Your Profile} > Advanced关闭此行为,复选框Set locale environment variables on startup)。

*字符编码(反映在区域设置ID的.{encoding}后缀中,通常为.UTF8)将与终端程序设置中配置的编码匹配(对于Terminal.app,请转到Preferences... > Settings > {Your Profile} > Advanced并更改Character encoding设置),如果支持(使用locale -a查看所有支持的语言/区域+编码组合)。

  • TerminaliTerm都默认为UTF-8,这是一个明智的选择。
  • 如果您的终端程序配置为使用 * 不支持的字符编码 *,则报告的区域设置ID在Terminal中将没有编码后缀(例如,只有en_US),并在iTerm中完全恢复到"C"区域设置-并且可能无法正常工作(Terminal仍然允许您从该编码 * 打印 * 非ASCII字符,但实用程序不会将其识别为字符,导致illegal byte sequence错误)。
  • 同样,如果您在System Preferences中配置了 * 不支持的主要语言和地理区域组合 *(例如,将“German”(de)与“United States”(US)组合,这将导致不支持的区域设置de_US),则只有LC_TYPE将与您的终端程序的编码匹配,其他LC_*类别将默认为"C"
  • 如果您需要 * 手动 * 设置区域设置,请运行:
  • export LANG={localeId}
  • export LC_ALL={localeId}

不同之处在于,export LANG=...为所有LC_*类别提供了一个 default,同时允许您选择性地覆盖它们,而export LC_ALL=... * 覆盖 * 所有LC_*类别。
支持的locale ID可以用locale -a列出;最好选择基于UTF-8的,例如de_CH.UTF-8
POSIX语言环境-本质上是一个仅ASCII的美国英语语言环境-可以通过"POSIX""C"选择。

*注意事项macOS自带的所有Unix工具都存在上述问题:它们不将NFC和NFD中的等效Unicode字符串识别为相同。除了这个问题,许多但不是所有的Unix实用程序原则上都支持UTF8多字节字符

  • 一个值得注意的例外从macOS 10.14开始-即根本不支持UTF8*的实用程序是**awk**;在早期的macOS版本中,sort也不支持UTF8(当以前使用的过时GNU实现被最近的BSD实现取代时,这一点发生了变化)。
dnph8jn4

dnph8jn42#

似乎我用osascript/AppleScript解决问题太快了-我可以在终端中过滤我的进程,但由于某种原因,它在我的脚本中不起作用。
所以我找到了解决这个问题的方法:如果我不能使用像ps、lsof、…之类的命令可靠地“grep”应用路径匹配我的脚本得到的路径作为param,然后我只需要在一个新的进程的帮助下重新生成它。
再一次,我的问题简而言之:
我的脚本获取一个应用程序路径作为参数。此路径包含变音。此外,应用程序有几个变体,命名不同,其中几个可能同时运行,但是脚本需要过滤它作为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

只要字符串中包含Umlaut,grep就会失败:

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

我的解决方案是在应用程序包中存在的文件上启动一个“tail &”进程,然后做一些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会更好...至少这个解决方案对我有用。再次感谢所有参与讨论这个问题的人!

sh7euo9m

sh7euo9m3#

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

$ 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

相关问题