![了解空间](https://linux22.com/image/229796/%E4%BA%86%E8%A7%A3%E7%A9%BA%E9%97%B4.png)
这是 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<file
、0<
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
对要删除的所有目录调用一次,而不是对每个目录调用一次。 - 我们使用
-prune
sofind
不会尝试进入rm
要删除的目录。 - 我使用
"("
代替,\(
因为我发现它更清晰。通常我会使用'('
behere,整个东西都在里面,'...'
所以我们不能使用它。 - 我们使用
zsh -c
而不是sh -c
因为sh
有一个错误特征,即当 glob 不匹配时,它们会按字面意思传递,因此 insh
,如果那里没有非隐藏文件,touch /Applications/*
将创建一个名为*
in的文件。/Applications
在 zsh 中,如果没有匹配项,则会出现错误。