所以
$(cat /etc/passwd)
结果为“没有这样的文件或目录”
和
"$(whereis cat)"
结果“未找到命令”
为什么命令替换没有按预期工作?
所以
$(cat /etc/passwd)
结果为“没有这样的文件或目录”
和
"$(whereis cat)"
结果“未找到命令”
为什么命令替换没有按预期工作?
我正在阅读 Dougherty 和 Robbins 合著的《sed & awk》一书。其中一个例子要求将输出通过管道传输到 shell 脚本:
sed -f nameState list | byState
但我发现,为了正常工作,我必须使用chmod授予“byState”脚本执行权限,并使用点斜杠调用它:
sed -f nameState list | ./byState
总是这样吗?这本书是 1997 年出版的,所以也许 shell 标准已经改变了?还是因为我使用的是bash而不是sh?
编辑:这是 byState 脚本:
#! /bin/sh
awk -F, ’{
print $4 ", " $0
}’ $* |
sort |
awk -F, ’
$1 == LastState {
print "\t" $2
}
$1 != LastState {
LastState = $1
print $1
print "\t" $2
}’
当尝试获取 shell 命令的 PID 时,可以使用脚本文件:
#!/bin/bash
ls -lha &
echo "$!"
然而,仅使用终端是不可能的。
/bin/bash -c 'ls -lha & ; echo "$!"'
这将返回错误:
/bin/bash:-c:第 1 行:意外标记“;”附近出现语法错误
手册中指出:
列表是由运算符、、或之一分隔的一个或多个管道序列,并可选地由、或 之一终止。在这些列表运算符中,和具有相同的优先级,后跟和,它们具有相同的优先级。
;
&
&&
||
;
&
<newline>
&&
||
;
&
列表中可能会出现一个或多个换行符序列, 而不是分号来分隔命令。
如果命令由控制操作符 终止
&
,则 shell 会在子 shell 中在后台执行该命令。shell 不会等待命令完成,返回状态为 0。以 分隔的命令;
按顺序执行;shell 等待每个命令依次终止。返回状态是最后执行的命令的退出状态。
因此我也尝试使用换行符,但无法使用它:
/bin/bash -c 'ls -lha & \n echo "$!"'
PS:我不会使用ls
命令,这是一个更复杂的脚本。这只是一个重现问题的最小示例。
我正在使用脚本来转码一堆电影,使用的是 HandBrake 的命令行版本。我在这里使用的是经过修改的脚本版本:
https://gist.github.com/ralphcrisostomo/56fc395b1646bd55aeeb2eb442043887
我遇到的问题是,转码在一小部分电影上不可重现地挂起。这伴随着 stderr 上的一个非常具体的错误。
我想要的是 1) 跟踪屏幕上的 stdout(而不是 stderr);2) 监视 stderr 中的错误情况;3) 如果满足错误条件,则终止 HandBrake 进程,记录有问题的文件并转到下一个。
目前转码命令如下:
echo "" | HandBrakeCLI -i "$ITEM" -o "$OUTPUT" --preset="$PRESET" $OPTS 2> /dev/null
满足1)。
遵循此处的建议:
我可以成功地 grep 错误流中的相关单词,例如
echo "" | HandBrakeCLI -i "$ITEM" -o "$OUTPUT" --preset="$PRESET" 2> >(grep -i error)
或者
echo "" | HandBrakeCLI -i "$ITEM" -o "$OUTPUT" --preset="$PRESET" 3>&2 2>&1 1>&3- | grep -i "error"
但是,所有这些都会将相关行从 stderr 打印到屏幕上。我不清楚如何使用它来执行特定命令,例如 kill HandBrakeCLI ; echo $ITEM >> err.log
我尝试使用 grep -q 来测试是否匹配成功,如下所示:
echo "" | HandBrakeCLI -i "$ITEM" -o "$OUTPUT" --preset="$PRESET" $OPTS 3>&2 2>&1 1>&3- | grep -q "error" && echo "error found" ; echo $ITEM >> failed.txt
但是脚本在遇到错误时会直接退出,而不会转到循环中的下一个项目。
任何帮助都值得感激!
已更新以回答 terdon 的问题:
我还对 HandBrake 调用中的初始 echo "" | 感到好奇。它位于查询顶部引用的原始脚本中,但我也不清楚为什么需要它。我在 MacOS 上运行它。完整脚本是
#!/bin/bash
SOURCE=$1
ENCODER=$2
OPTS=$3
TOTAL=$(find "$SOURCE" \! -name ".*" -type f | wc -l)
CURR=0
DESTINATION="PROCESSED"
ENCODER=${ENCODER:="hw"}
# Create directorys
[[ -d $DESTINATION ]] || mkdir -p $DESTINATION
while IFS= read -d '' -r ITEM
do
RES=`ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of default=nw=1 "$ITEM" | grep 'height*' | cut -c 8-`
FILE=${ITEM##*/}
EXT=${ITEM##*.}
EXT=$(echo $EXT | tr "[:upper:]" "[:lower:]")
OUTPUT="$DESTINATION/${FILE%.*}.$EXT"
# Get the right preset
if [[ "$RES" == "2160" ]] && [[ "$ENCODER" == "hw" ]]
then
PRESET="H.265 Apple VideoToolbox 2160p 4K"
elif [[ "$RES" == "2160" ]] && [[ "$ENCODER" == "sw" ]]
then
PRESET="Fast 2160p60 4K HEVC"
elif [[ "$RES" != "2160" ]] && [[ "$ENCODER" == "hw" ]]
then
PRESET="H.265 Apple VideoToolbox 1080p"
elif [[ "$RES" != "2160" ]] && [[ "$ENCODER" == "sw" ]]
then
PRESET="Fast 1080p30"
fi
echo "Processing" $FILE "using preset" $PRESET
echo "" | HandBrakeCLI -i "$ITEM" -o "$OUTPUT" --preset="$PRESET" $OPTS 2> /dev/null
CURR=$((CURR + 1))
echo "done" $CURR "of"$TOTAL
done< <(find "$SOURCE" \( -iname '*.mp4' -or -iname '*.mov' -or -iname '*.MP4' -or -iname '*.MOV' \) -print0)
更新 061224
根据 markp-fuso 的建议,我修改了调用 HandBrake 的行,如下所示:
echo "" | HandBrakeCLI -i "$ITEM" -o "$OUTPUT" --preset="$PRESET" 2> >(grep -i error && { pkill HandBrakeCLI ; echo $ITEM >> err.log; })
遇到错误时,会将相关错误打印到 stdout,但当前编码完成后,才会执行第二部分(例如终止进程并记录结果)。如果编码挂起并不断出现相同的错误,则不会执行命令的第二部分。如果我将行更改为:
echo "" | HandBrakeCLI -i "$ITEM" -o "$OUTPUT" --preset="$PRESET" 2> >(grep -q error && { pkill HandBrakeCLI ; echo $ITEM >> err.log; })
因此,遇到错误时 grep 命令会退出,整个脚本会退出,而不会处理循环中的任何其他项目。但奇怪的是,如果我在编码挂起时从另一个 shell 中终止 HandBrake 进程,它就会正确地转到下一个项目。
我从网上下载了一个脚本,其开头部分如下:
echo "alias myip='curl -s https://api.ipify.org/'" >> ~/.bash_profile
echo "alias myiplookup='ip2cc \$(curl -s https://api.ipify.org/)'" >> ~/.bash_profile
我在 macOS 机器上使用它,正如预期的那样,它.bash_profile
在我的$HOME
目录中创建了一个文件,其中包含相应的行:
alias myip='curl -s https://api.ipify.org/'
alias myiplookup='ip2cc $(curl -s https://api.ipify.org/)'
该机器上有两个 bash:
但无论我尝试其中哪一个,当我输入myip
或时myiplookup
,我都会收到command not found
消息。
以下是我一步步尝试过的操作:
bash
(适用于 bash 5.2)或/bin/bash
(适用于 bash 3.2)myip
.bash 显示消息command not found
。我做错什么了?
我在 10.15 Catalina 上安装了 Mac Ports 及其 bash 版本,但当我打开终端时,仍然看到 Apple 的旧 bash 版本,并且我已安装的所有 Mac 端口都不见了。它们在那里,但只在新 bash 版本中。
(1)在Apple的“系统偏好设置”的“用户和组”的“高级”部分中,我插入了Mac Ports bash路径/opt/local/bin/bash
,但它没有任何效果。
(2)将终端偏好设置更改为/opt/local/bin/bash
(参见下面 Tim Kennedy 的建议)也没有任何效果。
这是应用这两项更改之后的情况:
$ echo $BASH_VERSION
5.2.32(1)-release
$ bash -version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19)
Copyright (C) 2007 Free Software Foundation, Inc.
$ which bash
/bin/bash
还有一篇相关文章 «再次… bash 版本 mac 没有变化»,但它涵盖的是 Homebrew 版本而不是 Mac Ports,而且也没有解决这个问题。但它确实包含了一些其他有用的信息。
(3)应用上述文章的解决方案也无效:
$ chsh -s /opt/local/bin/bash
Changing shell for admin.
Password for admin:
chsh: /opt/local/bin/bash: non-standard shell
$ which bash
/bin/bash
(4)编辑文件的建议/etc/shells
也没有效果。
最后,Marc Wilson 关于 $PATH 的建议(见下文)让我找到了解决方案。我/etc/paths
最初的想法是这样的:
/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin
当我将 Mac Ports 目录放在/opt/local/bin
Apple 的 bash 位置之前时/bin
,使用sudo vi /etc/paths
,我终于成功了。现在,打开终端时,我得到:
$ echo $BASH_VERSION
5.2.32(1)-release
$ bash -version
GNU bash, version 5.2.32(1)-release (x86_64-apple-darwin19.6.0)
$ which bash
/opt/local/bin/bash
$ figlet it works
_ _ _
(_) |_ __ _____ _ __| | _____
| | __| \ \ /\ / / _ \| '__| |/ / __|
| | |_ \ V V / (_) | | | <\__ \
|_|\__| \_/\_/ \___/|_| |_|\_\___/
感谢大家!
当我们尝试使用 nameref 在函数中设置局部变量时遇到问题。
脚本代码如下:
#!/usr/bin/bash
msg=hello
myparam=''
superfunc () {
productfile=$1
local -n refmyparam=$2
}
superfunc $msg $myparam
echo $myparam
运行时我们收到错误:
line 7: local: `': not a valid identifier
我们使用GNU bash,版本 5.2.21
我尝试将存在异常的本地目录复制到 Bash 脚本中的另一个位置,但一直收到语法错误消息。我最终得到了一个可以复制我的问题的最小示例……这种情况只发生在函数内部。
因此这可以按预期工作:
#!/usr/bin/env bash
rm -rf /tmp/adir/ && mkdir --parents /tmp/adir/
shopt -s extglob dotglob
cp -ar /some/dir/subdirectory/!(tests|.git) /tmp/adir/
shopt -u extglob dotglob
...但这不起作用:
#!/usr/bin/env bash
a_function() {
rm -rf /tmp/adir && mkdir --parents /tmp/adir
shopt -s extglob dotglob
cp -ar /some/dir/subdirectory/!(tests|.git) /tmp/adir
shopt -u extglob dotglob
}
a_function
后者会引发语法错误:
./test.sh: line 6: syntax error near unexpected token `('
./test.sh: line 6: ` cp -ar /some/dir/subdirectory/!(tests|.git) /tmp/adir/'
Bash 版本:GNU bash,版本 5.2.15(1)-release(x86_64-pc-linux-gnu)
希望有人可以帮忙。
我发现 Linux 和 FreeBSD 上的 ssh 行为存在无法解释的差异,而根据手册页的简单语言,我预计 FreeBSD 的行为正是如此。因此,Linux 上发生了一些额外的事情,但我不知道是什么。
我在所有系统(包括 Linux 和 BSD)上都使用 bash 作为我的常规登录 shell。通过涉及 bash 启动文件的复杂多阶段设置,我为所有 bash 版本(无论是“登录”还是“交互式”shell,从 bash 手册页开头所说明的技术意义上讲)设置了环境变量。
现在,不同之处在于:当我使用命令ssh 进入 FreeBSD 服务器时,例如ssh freebsd-host printenv
,这些变量未设置。经过一番思考,这是我应该预料到的,因为在这种情况下 shell 既不是登录也不是交互的。但是,当我以同样的方式 ssh 进入Linux主机时,变量实际上已经设置了——这是一个明确的信号,表明.bash_profile
或.bashrc
文件已经以某种方式被理解了,尽管 shell 的状态为非。
我的问题当然是,为什么 Linux 会这样表现?是什么让 bash 启动在这种情况下运行?所有 sshd 配置都完全相同,并且接近上游默认值,包括PermitUserEnvironment=no
。如果重要的话,所有 Linux 都是 Debian 的变体。
一个额外的轶事:当我阅读一些手册页时,我偶然发现了这一点ssh -t host man man
,我发现远程 Linux 上没有什么不寻常的,但在远程 FreeBSD 上,手册页却变得非常狭窄——因为我的普遍设置MANWIDTH=100
不存在。
我有一个 BASH 脚本问题,我已经缩小了范围,但我无法理解我看到的内容。我想以简洁的方式获取数组的子字符串。这是我在 bash 中看到的内容,使用“set -x”,以便在执行命令时打印它们:
$: read -a colours
+ read -a colours
blue green red yellow pink purple
$: echo ${colours[@]}
+ echo blue green red yellow pink purple
blue green red yellow pink purple
$: expr substr "${colours[@]}" 16 6
+ expr substr blue green red yellow pink purple 16 6
expr: syntax error: unexpected argument ‘yellow’
$: expr substr \"${colours[@]}\" 16 6
+ expr substr '"blue' green red yellow pink 'purple"' 16 6
expr: syntax error: unexpected argument ‘yellow’
最后一次尝试expr substr
命令时,数组元素返回了奇怪的引号,那么这是从哪里来的呢?帮我理解一下这expr substr
是怎么回事,还是这是一个错误?
编辑:我在下面列出了一些平面引用字符串的期望行为,但为什么我不能以相同的方式引用数组?
$: all_colours=${colours[@]}
+ all_colours='blue green red yellow pink purple'
$: expr substr $all_colours 16 6
+ expr substr blue green red yellow pink purple 16 6
expr: syntax error: unexpected argument ‘yellow’
$: expr substr "$all_colours" 16 6
+ expr substr 'blue green red yellow pink purple' 16 6
yellow