在写shell脚本的时候,经常想分割一个字符串。这是一个非常简单的例子:
for dir in $(echo $PATH | tr : " "); do
[[ -x "$dir"/"$1" ]] && echo $dir
done
这将在 $PATH 中的每个目录中搜索与$1
.非常简单,它运行良好,但如果我的 $PATH 中的目录名称中包含空格,则会中断。
在出现循环分隔符时分割字符串的推荐方法是什么?
理想情况下,该解决方案能够在(相当)旧的 shell(即 ksh88)上运行。
答案1
显而易见的解决方案是使用 shell 分词,但要注意一些问题:
IFS=:
set -o noglob
for dir in $PATH''; do
dir=${dir:-.}
[ -x "${dir%/}/$1" ] && printf "%s\n" "$dir"
done
你需要set -o noglob
因为当变量不加引号时,两者分词和文件名生成(通配)对其执行,在这里你只需要分词(例如,在不太可能出现的情况下$PATH
contains /usr/local/*bin*
,您希望它在文件夹中查找/usr/local/*bin*
,而不是在/usr/local/bin
and /usr/local/sbin
... 中查找,如果PATH
contains /*/*/*/../../../*/*/*/*/../../../*/*/*/*
,您不希望它使您的计算机停机)
空$PATH
组件表示当前目录 ( .
),而不是/
.$dir/$1
在那种情况下是不正确的。在这种情况下,解决方法是编写$dir${dir:+/}$1
或更改为$dir
(.
当使用printf '%s\n' "$dir"
.
//foo
不一定与 相同/foo
,所以如果/
是 in $PATH
,则您不需要$dir/$1
,这将是//$1
。因此${dir%/}
要去掉尾部斜线。
然后,还有其他一些问题:
对于$PATH
,":"
是一个字段分隔器而对于$IFS
,它是一个字段终结者(是的,我知道,S
是为了S分离器,归咎于 ksh 和 POSIX 标准化了 ksh 行为)。
因此,如果$PATH
是/usr/bin:/bin:
(这是不好的做法,但仍然很常见),则意味着"/usr/bin"
, "/bin"
and ""
(即当前目录),而 shell 单词拆分(除 之外的所有 POSIX shell zsh
)会将其拆分为/usr/bin
and /bin
only。
如果$PATH
已设置但为空,则意味着:“仅在当前目录中查找”。而 shell(包括那些视为$IFS
分隔符的 shell)会将其扩展为空列表。
将上述内容附加''
到$PATH
上面可以解决这两个问题。
最后但并非最不重要的。如果$PATH
未设置,则具有特殊含义:查看系统默认搜索列表,不幸的是,根据你问谁(什么命令),这意味着不同的东西。
$ env -u PATH bash -c 'type usbipd'
usbipd is /usr/local/sbin/usbipd
$ env -u PATH ksh -c 'type usbipd'
ksh: whence: usbipd: not found
基本上,在您的脚本中,您必须猜测默认搜索路径在对您重要的上下文中是什么。
请注意,当未设置或为空时,POSIX 会保留未指定的行为$PATH
,因此不会为您提供帮助。这也意味着我上面所说的可能不适用于某些过去、当前或未来的 POSIX/Unix 系统。
简而言之,通过解析$PATH
来尝试找出命令的运行位置是一件棘手的事情。
有一个标准命令,即command
:
ls_path=$(command -v ls)
但人们可能会问:你为什么想知道?
现在将 IFS 恢复为其默认值:
oldIFS=$IFS
IFS=:
...
IFS=$oldIFS
在大多数情况下在实践中可以工作,但不保证 POSIX 可以工作。
原因是如果$IFS
之前未设置这意味着默认分裂行为(即在 POSIX shell 中,按空格、制表符或换行符分割),在这些命令之后,它将最终设置为空(这意味着没有分裂)。
另一个潜在的问题是,如果您概括该方法并在许多不同的函数中使用它,那么如果在...
上面的部分中,您正在调用一个执行相同操作的函数(创建$IFS
in的副本$oldIFS
),那么您将松开原来的$oldIFS
并恢复错误$IFS
。
相反,您可以在可能的情况下使用子 shell:
(
IFS=:
...
)
# only the subshell's IFS was affected, the parent still has its own IFS
我的方法是设置 $IFS (并打开set -o noglob
或关闭)每次我需要分词(这很少见)并且不费心恢复以前的值。当然,如果您的脚本调用其他人的代码,而该代码不遵循该实践并采用默认的分词行为,那么这将不起作用。
答案2
根据你的需要设置即可IFS
,让shell进行分词:
IFS=':'
for dir in $PATH; do
[ -x "$dir"/"$1" ] && echo $dir
done
这适用于bash
、dash
和ksh
,但仅在最新版本中进行了测试。
答案3
如果需要读取固定数量的字段到变量中,可以使用这个方法:
input="age:30"
IFS=':' read -r first_field second_field <<< "$input"
echo "$first_field"
echo "$second_field"
我发现它在格雷格的维基。
告诉-r
我们read
不应该将反斜杠视为特殊的。