了解空间

了解空间

这是 Ishmael Smyrnow 编写的一行 bash 脚本,用于清除 macOS 图标缓存:

sudo rm -rfv /Library/Caches/com.apple.iconservices.store; sudo find /private/var/folders/ \( -name com.apple.dock.iconcache -or -name com.apple.iconservices \) -exec rm -rfv {} \; ; sleep 3;sudo touch /Applications/* ; killall Dock; killall Finder

来源

我尝试了解如何将其改回多行版本。这是我现在所拥有的:

sudo rm -rfv /Library/Caches/com.apple.iconservices.store
sudo find /private/var/folders/ -name com.apple.dock.iconcache -exec rm -rfv {} \;
sudo find /private/var/folders/ -name com.apple.iconservices -exec rm -rfv {} \;
sleep 3
sudo touch /Applications/*
killall Dock
killall Finder

我确实知道在 Ishmael 的版本中,分号用于换行和\;文字分号。但是,在两个分号之间和星号后面留一个空格有什么用呢?删除它们安全吗?

 -exec rm -rfv {} \; ;
/Applications/* ;

答案1

在 shell 语言语法中何时使用或不使用空格并不是一件小事。

同样重要的是要注意不同类型的空间:

  • 空格和制表符用于分隔语法中的标记

  • 换行符分隔标记,但也分隔命令。例如:

    array=(
      foo bar
      baz
    )
    

    (来自 zsh 的语法现在已被其他几个 shell 支持)或 ksh 的(来自 C):

    (( 1
    + 2
    ))
    

    或者 POSIX sh(也可以来自 ksh):

    var=$((
      a + 
      b
    ))
    

    上面的空格、制表符或换行符可以分隔元素。

    但:

    cmd a
    cmd b
    

    运行两个单独的命令。

    哪里可以使用换行符代替空格并不总是很清楚,并且在 shell 之间可能有所不同。

  • 在某些 shell(包括 bash)中,其他一些字符被分类为空白的区域设置中的 可以用作语法中的标记分隔符,并且可以包含 U+00A0 等字符 不间断空格(仅在其分类为的区域设置中空白的并在 ) 的情况下编码为一个字节bash

然后是引用运算符 ( "...", '...', $'...', $"...", \),它们可用于删除字符在 shell 语法中具有的任何特殊含义。

还有许多特殊字符(主要();<>&|是在某种程度上`$),它们可以单独使用,也可以与其他字符组合形成语法中的标记,这些标记也可以分隔单词,因此不需要在它们周围添加空格。

例如,在 ksh 中,您可以编写:

[[(a == b||d>c)&&0<1 ]]&&(echo yes)|cat

代替:

[[ ( a == b || d > c ) && 0 < 1 ]] && ( echo yes ) | cat

因为||, (, &&, <, &&... 是可以分隔的单个标记。

在 bash(至少 5.1)中,你会发现你需要:

[[(a == b||d>c)&&0 <1 ]]&&(echo yes)|cat

否则你似乎遇到了某种错误:

$ bash -c '[[(a == b||d>c)&&0<1 ]]&&(echo yes)|cat'
bash: -c: line 1: unexpected token 284 in conditional command
bash: -c: line 1: syntax error near `&0<'
bash: -c: line 1: `[[(a == b||d>c)&&0<1 ]]&&(echo yes)|cat'

(可能是因为 Outside of [[...]]、 in cmd 0<file0<in 本身就是一个运算符,因此很可能被视为单独的标记)。

在:

foo \;;bar

没有歧义,这是一个foo单词、被视为分隔符的空格、一个引号;(与此处\;相同)以及标记和 then 。所以它的处理方式与:';'";";bar

foo ';' ; bar

添加空格不会造成损害,而且有助于提高可读性。

该代码也可以编写(并改进为):

sudo zsh -c '
  rm -rfv /Library/Caches/com.apple.iconservices.store
  find /private/var/folders/ \
    "("
       -name com.apple.dock.iconcache -o \
       -name com.apple.iconservices \
    ")" -prune -exec rm -rfv {} +
  sleep 3
  touch /Applications/*'
killall Dock
killall Finder

你会注意到的地方:

  • sudo仅运行一次,并将多行带引号的字符串作为参数传递给单独的 zsh 调用,其中所有字符都失去了对外壳的特殊含义。
  • \后面跟着换行符是上面没有提到的另一种特殊情况。这使得换行符消失(对于内壳;对于外壳,反斜杠和换行符位于单引号内,因此保持原义)。
  • 我们只调用一个find
  • 我们使用+not 来;分隔(对于find,而不是 shell)组成-exec执行命令的参数列表,因此rm对要删除的所有目录调用一次,而不是对每个目录调用一次。
  • 我们使用-prunesofind不会尝试进入rm要删除的目录。
  • 我使用"("代替,\(因为我发现它更清晰。通常我会使用'('behere,整个东西都在里面,'...'所以我们不能使用它。
  • 我们使用zsh -c而不是sh -c因为sh有一个错误特征,即当 glob 不匹配时,它们会按字面意思传递,因此 in sh,如果那里没有非隐藏文件,touch /Applications/*将创建一个名为*in的文件。/Applications在 zsh 中,如果没有匹配项,则会出现错误。

相关内容