作者:裕用ID
链接:https://www.zhihu.com/question/53295083/answer/135258024
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以下来自一个重度linux使用患者不请自来的回答。
先用简单的语言回答题主的问题:
shell程序中 2> /dev/null 代表什么意思?
答:“2> /dev/null” 代表忽略掉错误提示信息。
如题 2> 代表什么意思?讲错误输出删除?
答:“2>” 代表重定向操作错误提示信息。只有这两个字符并不能删除错误输出。
如果有正确的输出并赋值给i,i会得到正确的值吗?
答:i会得到正确的值。
上面的三个回答是我认为相对比较友好,容易理解的回答。
---------------------------------------我是啰哩啰唆回答的分割线-------------------------------------
2016-12-12 更新错误描述与更新排版
下面,咱们一起来看看这个命令操作涉及到的知识点(敲黑板。。。。)题主问题里描述的这条命令其实涉及到三部分的内容,如下图:
(原谅我奇怪的画风……)
下面的所有回到都是假设大家对linux没有太多的了解所作的,如有高手,打脸的时候请轻一点。+_+
1. 文件描述符
下面手打一段《linux shell脚本攻略》的描述(如有侵权我会删除的 T_T):
文件描述符是与文件输入、输出关联的整数。它们用来跟踪已打开的文件。最常见的文件描述符是stidin、stdout、和stderr。我们可以将某个文件描述符的内容重定向到另外一个文件描述符中。
《linux shell脚本攻略》
文件描述符我们常见的就是系统预留的0,1和2这三个,他们的意义分别有如下对应关系:
其中,shell编程里经常用到的就是描述符1,和描述符2。这样下面我们来举两个栗子,就知道神马是1和2了:
1 —— stdout
假设:在当前目录下我们“有且只有”一个文件名为 123.txt 的文本文件。这个时候我们运行下面的命令【ls 123.txt】:
我们就会获得一个标准输出stdout的输出结果“123.txt” 。
2 —— stderr
按照上面同样的假设,我们运行另外一跳命令【ls abc.txt】:
我们就会获得一个标准错误stderr的输出结果“ls:无法访问abc.txt:没有那个文件或目录”。
有同学应该会觉得,这两个事例好像跟1和2这两个阿拉伯数字好像没有关系。这个就要结合第二个知识点“重定向操作”来理解了。
2.重定向操作
书里找不到准确的关于重定向的描述,我很不要脸滴来说一下我的理解吧。重定向操作,其实就是通过在shell命令后面追加一个重定向操作符号,将shell命令对应的文件描述符输出的文本信息重新输入到另外一个指定文件的操作。
重定向操作符号有两个>和>>。尽管这两个操作符都可以将重定向到文件,但是前者会先清空文件,再写入内容;后者会将内容追加到现有文件的尾部。(对了,重定向的操作制定的文件如果原来不存在的话,重定向的操作会主动创建这个文件名的文件的)
下面我们结合第1个知识点文件描述符来举栗子吧。
重定向标准输出stdout
如上图所示,对比没有添加重定向的操作,ls命令在使用之后并没有将字符“123.txt”这个字符串打印到屏幕上。在紧接着的cat操作之后,我们可以看到本来应该输出字符串被记录在了stdout.txt这个文件里面了。
其实,对于标准输出的重定向操作,>等同于1>。上面栗子执行命令【ls 123.txt > stdout.txt】得到的效果也是一样的。
重定向标准错误stderr
如上图所示,文件描述符2,标准错误的重定向也是同样的原理被记录在了文件stderr.txt这个文件里面了。
描述符的重定向还有下面的几种用法:
你可以将stderr单独定向到一个文件,将stdout重定向到另一个文件:
cmd 2>stderr.txt 1>stdout.txt
也可以利用下面的方法,将stderr转换成stdout,使得stderr和stdout都被重新定向到同一个文件中:
cmd> output.txt 2>&1
或者采用这个方法(这个经常用到,我个人比较喜欢用这个,少写几个字符(*^__^*) )
cmd &> output.txt
cmd >& output.txt # 两个表达式效果一样哒~
(终于最后一个知识点,原来认真答题码字这么嘞。摔~)
3. linux特殊文件
手抄一段《linux shell脚本攻略》描述:
/dev/null是一个特殊的设备文件,这个文件接收到的任何数据都会被丢弃。因此,null这个设备通常也被成为位桶(bit bucket)或黑洞。
简单地理解就是,重定向操作给这个/dev/null文件的所有东西都会被丢弃。
因为这些文件描述符输出的字符串,总是会显示出来的。如果我们在shell编程的时候,操作到某一条命令的返回结果,我们不想要这个时候又不想让这个输出结果打印到屏幕上(打印错误,多不好看对不对^_^)我们就可以重定向到/dev/null这个文件来,由/dev/null这个文件负责处理后事。
这个丢弃的结果又不能粗暴的认为是删除错误输出,这个操作是一个丢弃重定向输入输出的操作。
形象地理解就是,ATM机打印的纸质流水账单(stdout和stderr)本来应该你来保存处理的,但是你又没有用放在手里(打印屏幕)又碍事,所以账单从你的手里重新被丢到了垃圾桶(/dev/null)了。但是,垃圾桶的垃圾是怎么处理的你是不知道的。
不知道上面的描述,答主是不是能明白这三个知识点了?只要理解了上面的三个点,其实答主的第三个问题很好滴能解决了。
问题3的思路
让一个变量获得命令输出的结果,是下面这样的处理:
i=$(ls 123.txt)
这样,i 就能获得命令【ls 123.txt】输出的标准输出。错误提示(标准错误)依然会打印到屏幕上显示。(万分感谢 @张超 同学在评论中指正的我的错误^_^)
针对答主的问题,应该是如下操作:
i=$(ls 123.txt 2> /dev/null)
这样的命令,ls命令如果出现了错误提示,就会被重定向到/dev/null垃圾桶去了。所以,屏幕上不会打印任何输出关于错误的提示字符。在这个命令的操作中,i 获得文件stdout标准输出,也就是文件述符1的屏幕输出结果"123.txt"。
如果,这个123.txt文件不存在,i 就肯定什么都拿不到,因为错误提示被/dev/null 吃了(划掉),被重定向丢弃了屏幕也不显示错误提示。所以,i 就是个什么都没有的空变量。基本就是如下效果一样:
i=''
天呐,不知不觉答题竟然答了一个晚上。第一次答题这么认真,走过路过的乡亲们,请赏个赞吧……码字好累啊……(/ □ \)
# Print Error without exit
print_stderr() {
echo "$1" >&2
}
# Print the status and exit 1
print_exit() {
print_stderr "$1"
[ ${NO_EXIT_ON_ERROR} ] || exit "${2:-1}"
}
# Setting Up Curl Options
CURL_OPS="-m5 -s"
# JSON Parser to Env Variables
jq_to_env(){
jq -r -j '.data
| to_entries
| if env.SANITIZE
then
map("\(.key|gsub("\\.";"_"))=\(.value)\n")
else
map("\(.key)=\(.value)\n")
end
| .[]
'
}
jq_to_env_v2(){
jq -r -j '.data.data
| to_entries
| if env.SANITIZE
then
map("\(.key|gsub("\\.";"_"))=\(.value)\n")
else
map("\(.key)=\(.value)\n")
end
| .[]
'
}
# Check if the any needed Variables are in place
sanity_checker(){
[ "${VAULT_ADDR}" ] || print_exit "VAULT_ADDR variable cant be fetched - check deploy"
[ "${VAULT_TOKEN}" ] || print_exit "VAULT_TOKEN variable cant be fetched - check deploy"
}
# Debugger - Print all the variables
debugger(){
[ "${DEBUG}" ] && CURL_OPS="${CURL_OPS} -vvv"
[ "${DEBUG}" ] && echo "VAULT_ADDR=${VAULT_ADDR}"
[ "${DEBUG}" ] && echo "VAULT_TOKEN=${VAULT_TOKEN}"
}
# Check if a kv secret engine is versioned
is_versioned () {
kv_version=$(curl -s ${CURL_OPS} -H "X-Vault-Token: ${VAULT_TOKEN}" ${VAULT_ADDR}/v1/sys/mounts | jq -r ".data.\"$1/\".options.version")
[ "$kv_version" == "2" ] && return 0 || return 1
}
# Read Properties from Vault in the path informed
get_properties(){
curl ${CURL_OPS} -H "X-Vault-Token: ${VAULT_TOKEN}" ${VAULT_ADDR}/v1/$1 | jq_to_env 2>/dev/null | sed "s/^/$2/"
}
get_properties_v2(){
curl ${CURL_OPS} -H "X-Vault-Token: ${VAULT_TOKEN}" ${VAULT_ADDR}/v1/$1 | jq_to_env_v2 2>/dev/null
}
# Read Properties in a Smart way - V2
get_secret_keys(){
curl ${CURL_OPS} -X LIST -H "X-Vault-Token: ${VAULT_TOKEN}" ${VAULT_ADDR}/v1/$1 | jq -r -j '.data.keys | join(" ")' 2>/dev/null
}
# Call Gatekeeper - Grabs wrapped token from gatekeeper using $MESOS_TASK_ID and unwraps it
get_token(){
WRAPPED_TOKEN=$(echo "{ \"task_id\":\"${MESOS_TASK_ID}\" }" | \
curl ${CURL_OPS} -X POST -H "Content-Type: application/json" -d @- http://${VLTGATEKEEPER_ADDRESS:-vltgatekeeper.service}/token | \
jshon -e token -u 2>/dev/null) || print_stderr "WRAPPED_TOKEN variable cant be fetched"
[ "$DEBUG" ] && echo "WRAPPED_TOKEN=${WRAPPED_TOKEN}"
[ "$WRAPPED_TOKEN" ] && {
VAULT_TOKEN=$(curl ${CURL_OPS} -H "X-Vault-Token: ${WRAPPED_TOKEN}" -X POST $VAULT_ADDR/v1/sys/wrapping/unwrap | \
jq --raw-output '.auth.client_token' 2>/dev/null) || print_stderr "VAULT_TOKEN variable cant be fetched"
[ "$DEBUG" ] && echo "VAULT_TOKEN=${VAULT_TOKEN}"
}
}
# Maintenance for taking out of LB before kill
shutdown() {
trap - TERM INT HUP
print_stderr "$(date): Killed by mesos. Possible reasons: deployment, OoM, no response for /internal/ping"
[ "${HOST}" ] && \
[ "${MESOS_CONTAINER_NAME}" ] && \
[ "${PORTS}" ] && {
for port in $(sed 's/,/ /g'<<<${PORTS})
do
port_int=$(env|sed -n "s/PORT_\([0-9]*\)=$port/\1/p")
consul_service_id="${HOST%%.*}:${MESOS_CONTAINER_NAME}:${port_int}"
print_stderr "$(date): Taking Consul service ${consul_service_id} out of load-balancing"
curl ${CURL_OPS} -X PUT "http://${HOST}:8500/v1/agent/service/maintenance/${consul_service_id}?enable=true"
done
}
# Wait for LB changes to take effect
sleep ${MAINTENANCE_SLEEP:-2}
# Properly shutdown application
print_stderr "$(date): Sending SIGTERM, waiting for application to terminate"
kill -TERM ${PID}
wait ${PID} # outer wait in start.sh was already interrupted by signal
TRAP_EXIT_CODE=$?
print_stderr "$(date): Shutdown finished, application exit code ${TRAP_EXIT_CODE}"
}
trap 'shutdown' TERM INT HUP
# Common Variables
HOST=${HOST:-$HOSTNAME}
FQDN=$(getent hosts $HOST)
FQDN=${FQDN##* }
export DOMAIN=${FQDN#*.}