为什么 bash 在输出中插入 "ls /" 的输出?
Why is bash inserting the output of "ls /" in output?
我在 bash 中遇到了一个相当神秘的错误,我怀疑它与 shell 扩展规则有关。
故事是这样的:在工作中,我的任务是记录一个庞大的内部网站,以协调公司资源。不幸的是,该代码非常丑陋,因为它已经超出了最初的目的,并且 "evolved" 成为协调公司工作的主要资源。
大部分代码是PHP。我写了一些帮助脚本来帮助我编写文档;例如,一个脚本提取 php 函数中使用的所有全局 php 变量。
所有这些脚本的中心是 "extract_function.sh" 脚本。基本上,给定一个 php 函数名和一个 php 源文件,它提取并输出 php 函数。
现在的问题是:不知何故,当脚本提取函数时,它基本上是在输出中随机插入 ls /
的输出。
例如:
$ ./extract_function my_function my_php_file.php
function my_function {
// php code
/etc
/bin
/proc
...
// more php code
}
更令人困惑的是,我只在一个特定文件的一个特定函数中发生了这种情况!现在,由于函数非常庞大(500 多行,当我说代码丑陋时我是认真的!),我一生都无法弄清楚是什么原因造成的,或者想出一个更简单的临时函数来产生这种行为。另外,公司政策不允许我分享实际代码。
但是,这是我的代码:
#!/usr/bin/env bash
program_name=$(basename [=13=]);
function_name=;
file_name=;
if [[ -z "$function_name" ]]; then
(>&2 echo "Usage: $program_name function_name [file]")
exit 1
fi
if [[ -z "$file_name" ]] || [ "$file_name" = "-" ]; then
file_name="/dev/stdin";
fi
php_lexer_file=$(mktemp)
trap "rm -f $php_lexer_file" EXIT
read -r -d '' php_lexer_text << 'EOF'
<?php
$file = file_get_contents("php://stdin");
$tokens = token_get_all($file);
foreach ($tokens as $token)
if ($token === '{')
echo PHP_EOL, "PHP_BRACKET_OPEN", PHP_EOL;
else if ($token == '}')
echo PHP_EOL, "PHP_BRACKET_CLOSE", PHP_EOL;
else if (is_array($token))
echo $token[1];
else
echo $token;
?>
EOF
echo "$php_lexer_text" > $php_lexer_file;
# Get all output from beginning of function declaration
extracted_function_start=$(sed -n -e "/function $function_name(/,$ p" < $file_name);
# Prepend <?php so that php will parse the file as php
extracted_function_file=$(mktemp)
trap "rm -f $extracted_function_file" EXIT
echo '<?php' > $extracted_function_file;
echo "$extracted_function_start" >> $extracted_function_file;
tokens=$(php $php_lexer_file < $extracted_function_file);
# I've checked, and at this point $tokens does not contain "/bin", "/lib", etc...
IFS=$'\n';
open_count=0;
close_count=0;
for token in $tokens; do # But here the output of "ls /" magically appears in $tokens!
if [ $token = "PHP_BRACKET_OPEN" ]; then
open_count=$((open_count+1))
token='{';
elif [ $token == "PHP_BRACKET_CLOSE" ] ; then
close_count=$((close_count+1))
token='}';
fi
echo $token;
if [ $open_count -ne 0 ] && [ $open_count -eq $close_count ]; then
break;
fi
done
是的,我知道我不应该使用 bash 来操纵 php 代码,但我基本上有两个问题:
1) 为什么 bash 这样做?
2) 还有,我该如何解决?
$tokens
中的一个标记是 *(或可以匹配多个文件的 glob 模式)。如果你不能安排令牌列表不包含 shell 元字符,你将需要跳过一些环节以避免扩展。一种可能的技术是使用 read -ra
将标记读入数组,这将使引用它们变得更容易。
我在 bash 中遇到了一个相当神秘的错误,我怀疑它与 shell 扩展规则有关。
故事是这样的:在工作中,我的任务是记录一个庞大的内部网站,以协调公司资源。不幸的是,该代码非常丑陋,因为它已经超出了最初的目的,并且 "evolved" 成为协调公司工作的主要资源。
大部分代码是PHP。我写了一些帮助脚本来帮助我编写文档;例如,一个脚本提取 php 函数中使用的所有全局 php 变量。
所有这些脚本的中心是 "extract_function.sh" 脚本。基本上,给定一个 php 函数名和一个 php 源文件,它提取并输出 php 函数。
现在的问题是:不知何故,当脚本提取函数时,它基本上是在输出中随机插入 ls /
的输出。
例如:
$ ./extract_function my_function my_php_file.php
function my_function {
// php code
/etc
/bin
/proc
...
// more php code
}
更令人困惑的是,我只在一个特定文件的一个特定函数中发生了这种情况!现在,由于函数非常庞大(500 多行,当我说代码丑陋时我是认真的!),我一生都无法弄清楚是什么原因造成的,或者想出一个更简单的临时函数来产生这种行为。另外,公司政策不允许我分享实际代码。
但是,这是我的代码:
#!/usr/bin/env bash
program_name=$(basename [=13=]);
function_name=;
file_name=;
if [[ -z "$function_name" ]]; then
(>&2 echo "Usage: $program_name function_name [file]")
exit 1
fi
if [[ -z "$file_name" ]] || [ "$file_name" = "-" ]; then
file_name="/dev/stdin";
fi
php_lexer_file=$(mktemp)
trap "rm -f $php_lexer_file" EXIT
read -r -d '' php_lexer_text << 'EOF'
<?php
$file = file_get_contents("php://stdin");
$tokens = token_get_all($file);
foreach ($tokens as $token)
if ($token === '{')
echo PHP_EOL, "PHP_BRACKET_OPEN", PHP_EOL;
else if ($token == '}')
echo PHP_EOL, "PHP_BRACKET_CLOSE", PHP_EOL;
else if (is_array($token))
echo $token[1];
else
echo $token;
?>
EOF
echo "$php_lexer_text" > $php_lexer_file;
# Get all output from beginning of function declaration
extracted_function_start=$(sed -n -e "/function $function_name(/,$ p" < $file_name);
# Prepend <?php so that php will parse the file as php
extracted_function_file=$(mktemp)
trap "rm -f $extracted_function_file" EXIT
echo '<?php' > $extracted_function_file;
echo "$extracted_function_start" >> $extracted_function_file;
tokens=$(php $php_lexer_file < $extracted_function_file);
# I've checked, and at this point $tokens does not contain "/bin", "/lib", etc...
IFS=$'\n';
open_count=0;
close_count=0;
for token in $tokens; do # But here the output of "ls /" magically appears in $tokens!
if [ $token = "PHP_BRACKET_OPEN" ]; then
open_count=$((open_count+1))
token='{';
elif [ $token == "PHP_BRACKET_CLOSE" ] ; then
close_count=$((close_count+1))
token='}';
fi
echo $token;
if [ $open_count -ne 0 ] && [ $open_count -eq $close_count ]; then
break;
fi
done
是的,我知道我不应该使用 bash 来操纵 php 代码,但我基本上有两个问题:
1) 为什么 bash 这样做?
2) 还有,我该如何解决?
$tokens
中的一个标记是 *(或可以匹配多个文件的 glob 模式)。如果你不能安排令牌列表不包含 shell 元字符,你将需要跳过一些环节以避免扩展。一种可能的技术是使用 read -ra
将标记读入数组,这将使引用它们变得更容易。