2016-08-29 71 views
3

我一直在使用內置的printf bash將字符串填充到特定寬度時出現「off-by-one」問題。內置printf bash可能的填充bug

看看下面的代碼:

#!/bin/bash 
# vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: 

# Only display motd if tty and not sudoing as root 
[ "$PS1" ] && [ "$EUID" -ne 0 ] || return 0 

# Run the entire function in its own subshell. 
# 
# The local keyword in functions prevents inheriting values. 
# The subshell prevents exporting them. 
# 
# Technically, local prevents exporting too. Only the vars that 
# could be used before initialized need to be declared local to 
# prevent the parent env from leaking into it. 
(
    MOTD_DEFAULT_VALUE="-" 

    function show_motd() { 

     local MOTD_AVAILABILITY_ZONE \ 
       MOTD_INSTANCE_ID \ 
       MOTD_INSTANCE_TYPE \ 
       MOTD_VPC_ID \ 
       MOTD_PUBLIC_IP 

     MOTD_AWS_METADATA_URL="http://169.254.169.254/latest/meta-data" 

     # Detect if inside AWS 
     MOTD_INTERFACE_PRIMARY_MAC="$(curl -s --connect-timeout 0.1 --max-time 0.1 ${MOTD_AWS_METADATA_URL}/network/interfaces/macs/ 2>/dev/null | sed -n 1p | cut -c-17)" 
     if [ -n "${MOTD_INTERFACE_PRIMARY_MAC}" ]; then 
      MOTD_AVAILABILITY_ZONE="$(curl -s ${MOTD_AWS_METADATA_URL}/placement/availability-zone 2>/dev/null)" 
      MOTD_INSTANCE_ID="$(curl -s ${MOTD_AWS_METADATA_URL}/instance-id 2>/dev/null)" 
      MOTD_INSTANCE_TYPE="$(curl -s ${MOTD_AWS_METADATA_URL}/instance-type 2>/dev/null)" 

      MOTD_VPC_ID="$(curl -s ${MOTD_AWS_METADATA_URL}/network/interfaces/macs/${MOTD_INTERFACE_PRIMARY_MAC}/vpc-id 2>/dev/null)" 
      [[ "${MOTD_VPC_ID}" == *'Not Found'* ]] && MOTD_VPC_ID="" 

      MOTD_PUBLIC_IP="$(curl -s ${MOTD_AWS_METADATA_URL}/public-ipv4 2>/dev/null)" 
      [[ "${MOTD_PUBLIC_IP}" == *'Not Found'* ]] && MOTD_PUBLIC_IP="" 
     fi 

     MOTD_OS="$(cat /etc/system-release | sed 's/ release//g' 2>/dev/null)" 
     [ -z "${MOTD_OS}" ] && MOTD_OS="$(cat /etc/os-release | grep 'PRETTY_NAME' | cut -d\" -f2 2>/dev/null)" 

     MOTD_HOSTNAME="$(hostnamectl --static 2>/dev/null)" 
     if [ -z "${MOTD_HOSTNAME}" ]; then 
      MOTD_HOSTNAME="$(hostnamectl --transient 2>/dev/null)" 
      if [ -z "${MOTD_HOSTNAME}" ]; then 
       MOTD_HOSTNAME="$(hostname 2>/dev/null)" 
      fi 
     fi 

     if [ -z "${MOTD_PUBLIC_IP}" ]; then 
      MOTD_PUBLIC_IP="$(ip -4 addr show scope global primary | sed -n 5p | cut -d\ -f6 | cut -d/ -f1 2>/dev/null)" 
     fi 

     MOTD_GATEWAY_IP="$(curl -s http://checkip.amazonaws.com 2>/dev/null)" 
     MOTD_PRIVATE_IP="$(ip -4 addr show scope global primary | sed -n 2p | cut -d\ -f6 | cut -d/ -f1 2>/dev/null)" 
     MOTD_TOTAL_CPUS="$(grep processor /proc/cpuinfo | wc -l 2>/dev/null)" 
     MOTD_TOTAL_DISKS="$(df -h | grep '^\/dev\/' | wc -l 2>/dev/null)" 
     MOTD_TOTAL_DISK_USED="$(df -h | grep '^\/dev/' | sed -n 1p | awk '{print $3, "/", $2, "(" $5 ")"}' 2>/dev/null)" 
     MOTD_TOTAL_MEMORY="$(free -h | awk '{print $2}' | sed -n 2p 2>/dev/null)" 

     if [ "${MOTD_PUBLIC_IP}" = "${MOTD_GATEWAY_IP}" ]; then 
      MOTD_GATEWAY_IP="" 
     fi 

     [ -z "${MOTD_AVAILABILITY_ZONE}" ] && MOTD_AVAILABILITY_ZONE="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_GATEWAY_IP}" ] && MOTD_GATEWAY_IP="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_HOSTNAME}" ] && MOTD_HOSTNAME="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_INSTANCE_ID}" ] && MOTD_INSTANCE_ID="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_INSTANCE_TYPE}" ] && MOTD_INSTANCE_TYPE="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_OS}" ] && MOTD_OS="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_PRIVATE_IP}" ] && MOTD_PRIVATE_IP="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_PUBLIC_IP}" ] && MOTD_PUBLIC_IP="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_TOTAL_CPUS}" ] && MOTD_TOTAL_CPUS="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_TOTAL_DISKS}" ] && MOTD_TOTAL_DISKS="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_TOTAL_DISK_USED}" ] && MOTD_TOTAL_DISK_USED="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_TOTAL_MEMORY}" ] && MOTD_TOTAL_MEMORY="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_VPC_ID}" ] && MOTD_VPC_ID="${MOTD_DEFAULT_VALUE}" 

     printf "$(tput sgr0;tput setaf 124)%-$(tput cols)s$(tput sgr0)\n%-$(tput cols)s\n" \ 
      "This system is operated and monitored by a private party." "" 
     printf " %sHostname: %s\n" "$(tput bold;tput setaf 216)" "$(tput sgr0;tput setaf 180;echo ${MOTD_HOSTNAME})" 
     printf "   %sOS: %-47s " "$(tput bold;tput setaf 75)" "$(tput sgr0;tput setaf 32;echo ${MOTD_OS})" 
     printf "  %sTotal CPUs: %s\n" "$(tput bold;tput setaf 48)" "$(tput sgr0;tput setaf 78;echo ${MOTD_TOTAL_CPUS})" 
     printf " %sPublic IP: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_PUBLIC_IP})" 
     printf " %sTotal Memory: %s\n" "$(tput bold;tput setaf 48)" "$(tput sgr0;tput setaf 78;echo ${MOTD_TOTAL_MEMORY})" 
     printf " %sPrivate IP: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_PRIVATE_IP})" 
     printf "  %sTotal Disks: %s\n" "$(tput bold;tput setaf 48)" "$(tput sgr0;tput setaf 78;echo ${MOTD_TOTAL_DISKS})" 
     printf " %sGateway IP: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_GATEWAY_IP})" 
     printf " %sRoot Vol. Used: %s\n" "$(tput bold;tput setaf 48)" "$(tput sgr0;tput setaf 78;echo ${MOTD_TOTAL_DISK_USED})" 
     printf "%sInstance Id: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_INSTANCE_ID})" 
     printf "  %sAvail. Zone: %s\n" "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_AVAILABILITY_ZONE})" 
     printf "  %sVPC Id: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_VPC_ID})" 
     printf " %sInstance Type: %s\n" "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_INSTANCE_TYPE})" 
     printf "$(tput sgr0)%-$(tput cols)s\n" "" 
    } 

    show_motd || true 
) 

注意以下行明確:

 printf "   %sOS: %-47s " "$(tput bold;tput setaf 75)" "$(tput sgr0;tput setaf 32;echo ${MOTD_OS})" 

%-47s是從各地擁有,因爲這似乎解決我的問題%-48s不同描述,但是,如果我恢復它匹配其餘所有行使用%-48s排他性,以下問題發生:

This system is operated and monitored by a private party. 

    Hostname: nova.localdomain 
     OS: Fedora 24 (Twenty Four)     Total CPUs: 6 
    Public IP: -         Total Memory: 7.8G 
Private IP: 192.168.1.100       Total Disks: 2 
Gateway IP: -         Root Vol. Used: 11G/32G (36%) 
Instance Id: -          Avail. Zone: - 
    VPC Id: -         Instance Type: - 

請注意Total CPUs:單元格是如何向右移動一個字符的,但在該行上使用%-47s可解決該問題,以便與該列中的其餘單元格齊平。

我想知道是否有人可以向我解釋爲什麼這是和如何解決問題,因此所有的printf行使用相同的填充值?

對於它的價值,我的bash的版本是:

GNU bash, version 4.3.42(1)-release (x86_64-redhat-linux-gnu) 

謝謝!

+1

它沒有'大膽'和類似的東西工作嗎? – choroba

+0

刪除這些'printf'行中的所有'tput'調用可以解決這個問題。然而,我不明白,所有的'tput'調用都是用粗體和顏色來平衡的,沒有一個調用'tput'的次數不是其他的。 –

+2

看起來問題可能在於'tput'將'setaf'命令簡單地轉換爲'\ 033 [0; xxxm',其中'xxx'在其他行中是'246',而在有問題的行中是'32'。如果是這樣,那麼這將解釋一個差異,因爲'32'是2個字符,而'246'是3個字符。 –

回答

1

我在試錯後自己解決了這個問題。

該問題是由於通過在printf行中調用tput而創建的豐富格式引起的。由於基於顏色ID的字符串長度(例如246(3的長度)對32(2的長度))而生成的格式化字符串的長度不同,這導致對齊問題。

補丁上面的原始文件低於:

diff --git a/motdA.sh b/motdB.sh 
index b73c6ab..fa3e609 100644 
--- a/motdA.sh 
+++ b/motdB.sh 
@@ -42,6 +42,8 @@ 
     MOTD_OS="$(cat /etc/system-release | sed 's/ release//g' 2>/dev/null)" 
     [ -z "${MOTD_OS}" ] && MOTD_OS="$(cat /etc/os-release | grep 'PRETTY_NAME' | cut -d\" -f2 2>/dev/null)" 

+  MOTD_OS_COLOR_B="$(cat /etc/os-release | grep 'ANSI_COLOR' | cut -d\" -f2 2>/dev/null)" 
+ 
     MOTD_HOSTNAME="$(hostnamectl --static 2>/dev/null)" 
     if [ -z "${MOTD_HOSTNAME}" ]; then 
      MOTD_HOSTNAME="$(hostnamectl --transient 2>/dev/null)" 
@@ -70,6 +72,7 @@ 
     [ -z "${MOTD_HOSTNAME}" ] && MOTD_HOSTNAME="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_INSTANCE_ID}" ] && MOTD_INSTANCE_ID="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_INSTANCE_TYPE}" ] && MOTD_INSTANCE_TYPE="${MOTD_DEFAULT_VALUE}" 
+  [ -z "${MOTD_OS_COLOR_B}" ] && MOTD_OS_COLOR_B="0;32" 
     [ -z "${MOTD_OS}" ] && MOTD_OS="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_PRIVATE_IP}" ] && MOTD_PRIVATE_IP="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_PUBLIC_IP}" ] && MOTD_PUBLIC_IP="${MOTD_DEFAULT_VALUE}" 
@@ -79,20 +82,25 @@ 
     [ -z "${MOTD_TOTAL_MEMORY}" ] && MOTD_TOTAL_MEMORY="${MOTD_DEFAULT_VALUE}" 
     [ -z "${MOTD_VPC_ID}" ] && MOTD_VPC_ID="${MOTD_DEFAULT_VALUE}" 

+  MOTD_OS_COLOR_A="1;${MOTD_OS_COLOR_B:2}" 
+ 
+  MOTD_PADDING="48" # Arbitrary length 
+  MOTD_PADDING_OS="$((${MOTD_PADDING}-${#MOTD_OS_COLOR_B}-6))" # 6 comes from length of "\033[m" 
+ 
     printf "$(tput sgr0;tput setaf 124)%-$(tput cols)s$(tput sgr0)\n%-$(tput cols)s\n" \ 
      "This system is operated and monitored by a private party." "" 
     printf " %sHostname: %s\n" "$(tput bold;tput setaf 216)" "$(tput sgr0;tput setaf 180;echo ${MOTD_HOSTNAME})" 
-  printf "   %sOS: %-47s " "$(tput bold;tput setaf 75)" "$(tput sgr0;tput setaf 32;echo ${MOTD_OS})" 
+  printf "   %sOS: %-${MOTD_PADDING_OS}s " "$(echo -en "\033[${MOTD_OS_COLOR_A}m")" "$(echo -en "\033[${MOTD_OS_COLOR_B}m${MOTD_OS}")" 
     printf "  %sTotal CPUs: %s\n" "$(tput bold;tput setaf 48)" "$(tput sgr0;tput setaf 78;echo ${MOTD_TOTAL_CPUS})" 
-  printf " %sPublic IP: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_PUBLIC_IP})" 
+  printf " %sPublic IP: %-${MOTD_PADDING}s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_PUBLIC_IP})" 
     printf " %sTotal Memory: %s\n" "$(tput bold;tput setaf 48)" "$(tput sgr0;tput setaf 78;echo ${MOTD_TOTAL_MEMORY})" 
-  printf " %sPrivate IP: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_PRIVATE_IP})" 
+  printf " %sPrivate IP: %-${MOTD_PADDING}s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_PRIVATE_IP})" 
     printf "  %sTotal Disks: %s\n" "$(tput bold;tput setaf 48)" "$(tput sgr0;tput setaf 78;echo ${MOTD_TOTAL_DISKS})" 
-  printf " %sGateway IP: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_GATEWAY_IP})" 
+  printf " %sGateway IP: %-${MOTD_PADDING}s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_GATEWAY_IP})" 
     printf " %sRoot Vol. Used: %s\n" "$(tput bold;tput setaf 48)" "$(tput sgr0;tput setaf 78;echo ${MOTD_TOTAL_DISK_USED})" 
-  printf "%sInstance Id: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_INSTANCE_ID})" 
+  printf "%sInstance Id: %-${MOTD_PADDING}s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_INSTANCE_ID})" 
     printf "  %sAvail. Zone: %s\n" "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_AVAILABILITY_ZONE})" 
-  printf "  %sVPC Id: %-48s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_VPC_ID})" 
+  printf "  %sVPC Id: %-${MOTD_PADDING}s " "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_VPC_ID})" 
     printf " %sInstance Type: %s\n" "$(tput bold;tput setaf 250)" "$(tput sgr0;tput setaf 246;echo ${MOTD_INSTANCE_TYPE})" 
     printf "$(tput sgr0)%-$(tput cols)s\n" "" 
    } 

謝謝大家誰貢獻!