Bash 是否有一个选项可以诊断(并可选择中止执行)“扩展为空值”变量?
示例(假设):
$ bash -c 'echo $x' --xxx
bash: line 1: variable 'x' expanded to empty value
提问的原因:这样的选项在调试脚本时可能会有用。
例如,在某些脚本中,所有变量都应扩展为非空值。因此,使用选项来检测(并可选地中止执行)“扩展为空值”的变量对于这种情况可能很有用。
我已经在set
内置中搜索过此选项,但一无所获。
来自man bash
(GNU bash,版本 5.2.21(1)-release(x86_64-pc-cygwin)):
set [-abefhkmnptuvxBCEHPT] [-o option-name] [--] [-] [arg ...]
-a Each variable or function that is created or modified is given the export
attribute and marked for export to the environment of subsequent commands.
export [-fn] [name[=word]] ...
export -p
The supplied names are marked for automatic export to the environment of
subsequently executed commands.
这里我们看到,export
没有“给出导出属性”,而却set -a
给出了。为什么?这是一个疏忽吗?
附加问题:“导出”和“自动导出”有什么区别?(都应该是“导出”吗?都应该是“自动导出”吗?)
设想:
$ process(){ echo "[$1] [$2] [$3]" ; } ; export -f process
$ process "x" "" "a.txt"
[x] [] [a.txt]
在这里我们看到第二个参数是空字符串(预期)。
$ find -name "*.txt" -print | SHELL=$(type -p bash) parallel process "x" ""
[x] [./a.txt] []
[x] [./b.txt] []
[x] [./c.txt] []
在这里我们看到第二个参数是 find 的输出(意外)。
预期输出:
[x] [] [./a.txt]
[x] [] [./b.txt]
[x] [] [./c.txt]
怎么修?
""
注意:如果第二个参数从to更改"y"
,则 find 的输出将作为第三个参数出现(预期):
$ find -name "*.txt" -print | SHELL=$(type -p bash) parallel process "x" "y"
[x] [y] [./a.txt]
[x] [y] [./b.txt]
[x] [y] [./c.txt]
为什么find 的输出不""
作为第三个参数出现?
UPD:看来解决方案是\"\"
:
$ find -name "*.txt" -print | SHELL=$(type -p bash) parallel process "x" \"\"
[x] [] [./a.txt]
[x] [] [./b.txt]
[x] [] [./c.txt]
但是,我不确定这是否是正确的通用解决方案。这是反例:
$ VAR="" ; find -name "*.txt" -print | SHELL=$(type -p bash) parallel process "x" "$VAR"
[x] [./a.txt] []
[x] [./b.txt] []
[x] [./c.txt] []
设想:
$ cat libs.txt
lib.a
lib1.a
$ cat t1a.sh
f1()
{
local lib=$1
stdbuf -o0 printf "job for $lib started\n"
sleep 2
stdbuf -o0 printf "job for $lib done\n"
}
export -f f1
/usr/bin/time -f "elapsed time %e" cat libs.txt | SHELL=$(type -p bash) parallel --line-buffer --jobs 2 f1
$ bash t1a.sh
elapsed time 0.00
job for lib.a started
job for lib1.a started
job for lib.a done
job for lib1.a done
在这里我们看到它elapsed time 0.00
出现在命令输出之前。为什么?
如何使出现在命令输出elapsed time 0.00
之后?
设想:
$ cat libs.txt
lib.a
lib1.a
$ cat t1a.sh
f1()
{
local lib=$1
stdbuf -o0 printf "job for $lib started\n"
sleep 2
stdbuf -o0 printf "job for $lib done\n"
}
export -f f1
cat libs.txt | SHELL=$(type -p bash) parallel --jobs 2 f1
调用及输出:
$ time bash t1a.sh
job for lib.a started
job for lib.a done
job for lib1.a started
job for lib1.a done
real 0m2.129s
user 0m0.117s
sys 0m0.033s
这里我们看到 的执行f1
确实是并行的 ( real 0m2.129s
)。
但是,诊断输出看起来执行是连续的。
我期望得到以下诊断输出:
job for lib.a started
job for lib1.a started
job for lib.a done
job for lib1.a done
为什么诊断输出看起来像顺序执行而不是并行执行?
如何修复诊断输出,使其看起来像并行执行?
设想:
$ tree .
.
├── x1.txt
├── x2.txt
└── x3.txt
0 directories, 3 files
$ find . -name "x1.txt" -prune -o -name "*.txt"
./x1.txt
./x2.txt
./x3.txt
$ find . -name "x1.txt" -prune -o -name "*.txt" -print0
./x2.txt./x3.txt
在这里我们看到这-print0
确实会影响搜索结果。使困惑。
为什么存在-print0
导致find ...
不打印x1.txt
?
-print0
为什么没有find ...
打印x1.txt
?
从man bash
:
-n string
True if the length of string is non‐zero.
例子:
# expected
$ var=""; [ -n "$var" ]; echo $?
1
# unexpected?
$ var=""; [ -n $var ]; echo $?
0
在这里我们看到,-n
包含空字符串的不带引号的变量返回 true。为什么?
为什么$var
这里需要引用呢?
文件上的 diff3 没有发现差异:
$ grep -P '\[\[.*?\]\]' -o intro.tex | sort > A.txt
$ grep -P '\[\[.*?\]\]' -o intro.tex | sort | uniq > B.txt
$ grep '\\pnum %% \[\[' intro.tex | sed 's/\\pnum %% //' | sort > C.txt
$ diff3 A.txt B.txt C.txt | wc -l
0
diff3 在运行相同命令的进程替换上发现了差异:
$ diff3 \
<(grep -P '\[\[.*?\]\]' -o intro.tex | sort) \
<(grep -P '\[\[.*?\]\]' -o intro.tex | sort | uniq) \
<(grep '\\pnum %% \[\[' intro.tex | sed 's/\\pnum %% //' | sort) | wc -l
95
为什么?有任何想法吗?
最小复制器:
$ echo test > a
$ diff3 a a a
$ diff3 <(cat a) <(cat a) <(cat a)
====1
1:1c
test
2:0a
3:0a
如何包装命令以测量其经过的时间?
目前我使用它eval
:
do_cmd_named()
{
local name=$1
local cmd=$2
echo "$name"
local start_time=$(date +%s)
eval "$cmd 2>&1"
local exit_status=$?
local end_time=$(date +%s)
local elapsed_time_sec=$((end_time-start_time))
local elapsed_time_min_sec=$(date -ud "@$elapsed_time_sec" +'%M:%S')
if [[ $exit_status -ne 0 ]]
then
echo "$name failed with exit status $exit_status (elapsed time $elapsed_time_min_sec)"
return $exit_status
else
echo "$name done (elapsed time $elapsed_time_min_sec)"
fi
}
job()
{
sleep 1
}
do_cmd_named "do job" "job"
这导致:
do job
do job done (elapsed time 00:01)
对于我的情况,这种方法几乎有效。但是,这种方法被认为是不好的,因为它违反了BashFAQ的一些规则。例如, BashFAQ #50中的“不要将代码放在变量中” (另请参见BashFAQ #48)。
所以,问题是:如何正确地做到这一点?
如何获取scp
版本?
已经尝试过:
$ scp --version
scp: unknown option -- -
usage: scp [-346ABCOpqRrsTv] [-c cipher] [-D sftp_server_path] [-F ssh_config]
[-i identity_file] [-J destination] [-l limit]
[-o ssh_option] [-P port] [-S program] source ... target
$ man scp | grep version
<nothing>
这是否意味着scp
没有版本?
设想:
$ echo "Hello World" > /dev/stderr
Hello World
$ echo "Hello World" > /dev/stdout
$ uname -a
CYGWIN_NT-10.0 xxx 3.3.4(0.341/5/3) 2022-01-31 19:35 x86_64 Cygwin
为什么echo "Hello World" > /dev/stdout
什么都不打印?如何解决问题?
UPD。
echo "Hello World"
打印正常吗?
是的:
$ echo "Hello World"
Hello World
如果没有,你是否
exec >/dev/null
在 shell 中调用或类似的?
不。
UPD2。找到它停止工作的地方:
$ clang t554.c -std=c11 -pedantic -Wall -Wextra -c -S -O3 -o /dev/stdout
.text
<asm code>
# "exxxtern" was a typo
$ clang t554.c -std=c11 -pedantic -Wall -Wextra -c -S -O3 -o /dev/stdout
t554.c:6:3: error: use of undeclared identifier 'exxxtern'
exxxtern int xxx;
^
1 error generated.
TASKING+pavel.morozkin@SPBPC023 ~
$ clang t554.c -std=c11 -pedantic -Wall -Wextra -c -S -O3 -o /dev/stdout
# nothing is printed for the 1st time
UPD3。我可以在另一台机器上用 clang 重现它:
$ clang t455.c -S -o /dev/stdout
.text
<asm code>
# introduce the error
$ clang t455.c -S -o /dev/stdout
t455.c:26:1: error: unknown type name 'x'
x
^
t455.c:26:2: error: expected identifier or '('
x
^
2 errors generated.
# fix the error
$ clang t455.c -S -o /dev/stdout
# nothing is printed
$ clang --version
clang version 8.0.1 (tags/RELEASE_801/final)
设想:
$ cat t0.txt
xxx
yyy
$ man grep | grep '\-\-null\-data' -A1
-z, --null-data
Treat the input as a set of lines, each terminated by a zero byte (the ASCII NUL character) instead of a newline.
$ grep -Pzo 'xxx\0yyy' t0.txt
<nothing>
$ grep -Pzo 'xxx\nyyy' t0.txt
xxx
yyy
那么,如果 grep “将输入视为一组行,每行都以零字节结尾”,那么为什么'xxx\0yyy'
不产生任何结果呢?
设想:
$ cat t0.txt
xxx
$ grep xxx t0.txt > t0.txt
grep: t0.txt: input file is also the output
# exit status 2
$ cat t0.txt
<nothing>
问题:如果输入文件也是输出并且存在状态是2
(发生错误),那么为什么要清除输入文件?
这个案例:
$ cat t01.txt
xxxyyyzzz
$ cat t01.txt | grep -Po '(?<=xxx).*(?=zzz)'
结果是:
yyy
这是预期的。
但是,这种情况:
$ cat t02.txt
xxx
yyy
zzz
$ cat t02.txt | grep -Pzo '(?<=xxx).*(?=zzz)'
结果是:
<nothing>
这是出乎意料的。
为什么以及如何解决?
例子:
$ eval echo "{x,y,z}\ --opt\; "
x --opt; y --opt; z --opt;
假设第二个列表是{1,2,3}
并且它的长度等于第一个(初始)列表的长度。
问题:
bash
:x --opt 1; y --opt 2; z --opt 3;
bash
制作(即从{x,y,z}
列表中引用元素):x --opt x; y --opt y; z --opt z;
单线是优选的。