AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / computer / 问题 / 1599019
Accepted
Benjamin Dennison
Benjamin Dennison
Asked: 2020-11-02 15:26:35 +0800 CST2020-11-02 15:26:35 +0800 CST 2020-11-02 15:26:35 +0800 CST

在脚本中设置“LANG=C LC_ALL=C”对 printf 中非英文字符的填充长度没有影响

  • 772

我正在将 Bash 脚本的输出格式化为表格,以便用于表示每列边界的管道字符相对于该列中最长的字符串间隔开(从而很好地排列所有管道字符)。

下面是相关的输出。请注意,对于“il2”和“ksp”行,两个管道字符是如何对齐的,但是在包含非英文字符 'ý' 的“iceland”行中,第二个管道字符相对左缩进一个空格到其他行。

iceland          | Mýrdalssandur Iceland                    | steam://rungameid/1248990                                                                   | iceland-Win64-Shipping.exe
il2              | IL 2 Sturmovik: 1946                      | steam://rungameid/15320                                                                     | il2fb.exe
ksp              | Kerbal Space Program                      | steam://rungameid/220200                                                                    | KSP_x64.exe

在脚本的最开始,我有以下内容:

oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C

最后,就在最后的“完成”声明之前,我有:

LANG=$oLang LC_ALL=$oLcAll

整个脚本没有其他语言设置更改。

如果在 shell 中我将$string变量设置为“Mýrdalssandur Iceland”并运行printf "%-14s is %2d char length\n" "'$string'" ${#string},我得到的字符长度为 21,但是当我运行时LANG=C LC_ALL=C,printf "%-14s is %2d char length\n" "'$string'" ${#string}我得到的字符长度为 22,这表明它实际上应该有所作为。

我创建了一个准系统脚本来测试填充,并且在该脚本中,更改LANG确实具有预期的效果,即使这些printf行包含在函数中,就像它们在所讨论的主脚本中一样。

在这一点上,我不知道为什么输出没有应用语言更改,因此感谢您提供任何帮助。

这是完整的脚本:

#!/bin/bash

oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C

#Config file location
listpath="/home/$USER/.config/play/"
config="$listpath"config
games="$listpath"games.list

if ! [ -f $config ] ; then
    echo "Config file not found at \"$config\""
    exit
fi

#Vars/arrs used for script
gamecmd="$1"
prgm=""
line=""
pathf=""
ctr=0
rctr=0
appID=0
taskname=""
result=""
task=""
answer=""
longestshort=0
longestlong=0
longestpathf=0
longestservicename=0
ip=`cat $config | sed -n -e 's/^.*ip=//p'`
wr=`cat $config | sed -n -e 's/^.*wr=//p'`
display=`cat $config | sed -n -e 's/^.*display=//p'`
ControlMyMonitor=`cat $config | sed -n -e 's/^.*ControlMyMonitor=//p'`
ControlMyMonitor=${ControlMyMonitor//\\//}
declare -a short
declare -a long
declare -a path
declare -a servicename

#Shortcut locations to make adding games easier
STEAM="steam://rungameid/"
DESKTOP=`cat $config | sed -n -e 's/^.*desktop=//p'`
APPDATA=`cat $config | sed -n -e 's/^.*appdata=//p'`

function check_for_duplicate()
{
    dctr=0

    #Set delimiter to comma
    IFS=','

    while IFS= read -r line
    do
        read -a strarr <<< "$line"

        for (( n=0; n < ${#strarr[*]}; n++))
        do
            if [ "${strarr[n]}" == "$1" ]
            then
                get_record $dctr "$line"
                echo -e "Duplicate entry for \""$1"\" found:"
                echo
                print_verbose_header
                echo
                pathf="${path[$dctr]}"
                format_pathf
                    print_verbose_record "${short[$dctr]}" "${long[$dctr]}" "$pathf" "${servicename[$dctr]}"

                exit
            fi
        done
        ((dctr++))
    done < $games
}
function format_pathf()
{
    pathf="${pathf//\"/}"
    pathf="${pathf//%STEAM%/$STEAM}"
    pathf="${pathf//%DESKTOP%/$DESKTOP}"
    pathf="${pathf//%APPDATA%/$APPDATA}"
}
function get_normal_header_spacing()
{
    for (( IFS=" " i=0; i<$ctr; i++ ))
    do
        if [ "${#short[i]}" -gt "$longestshort" ]
        then
            longestshort="${#short[i]}"
        fi
        if [ "${#long[i]}" -gt "$longestlong" ]
        then
            longestlong="${#long[i]}"
        fi
        done
}
function get_verbose_header_spacing()
{
    for (( IFS=" " i=0; i<$ctr; i++ ))
    do
        if [ "${#short[i]}" -gt "$longestshort" ]
        then
            longestshort="${#short[i]}"
        fi
        if [ "${#long[i]}" -gt "$longestlong" ]
        then
            longestlong="${#long[i]}"
        fi

        pathf="${path[i]}"
        format_pathf

        if [ "${#pathf}" -gt "$longestpathf" ]
        then
            longestpathf="${#pathf}"
        fi
        if [ "${#servicename[i]}" -gt "$longestservicename" ]
        then
            longestservicename="${#servicename[i]}"
        fi
        done
}
function get_record()
{
    short[$1]=$(echo "$2" | grep -Po '([^,]+)' | head -1)
    long[$1]=$(echo "$2" | grep -Po '([^,]+)' | head -2 | tail -1)
    path[$1]=$(echo "$2" | grep -Po '([^,]+)' | head -3 | tail -1)
    servicename[$1]=$(echo "$2" | grep -Po '([^,]+)' | head -4 | tail -1)
}
function print_normal_header()
{
    printf "%-${longestshort}s   %-${longestlong}s\n" "Command" "Game Title"

    for (( i=0; i<$(($longestshort + $longestlong + 2)); i++ ))
    do
        echo -n "-"
    done
}
function print_verbose_header()
{
    printf "%-${longestshort}s   %-${longestlong}s   %-${longestpathf}s   %s\n" "Command" "Game Title" "Path" "Task Name"
        
    for (( i=0; i<$(($longestshort + $longestlong + $longestpathf + $longestservicename + 4)); i++ ))
    do
        echo -n "-"
    done
}
function print_normal_record()
{
    printf "%-${longestshort}s | %-${longestlong}s\n" "$1" "$2"
}
function print_verbose_record()
{
    printf "%-${longestshort}s | %-${longestlong}s | %-${longestpathf}s | %s\n" "$1" "$2" "$3" "$4"
}
function print_help()
{
    echo -e "Launch program on remote Windows PC and change monitor inputs automatically.\n"
    echo -e "USAGE"
    echo -e "-----"
    echo -e
    echo -e "\e[3mplay [COMMAND]\e[0m"
    echo -e "Launch the game defined by the COMMAND parameter.\n"
    echo -e "\e[3mplay -l\e[0m"
    echo -e "Print a formatted table of games saved to the games.list file excluding file paths.\n"
    echo -e "\e[3mplay -lv\e[0m"
    echo -e "Print a formatted table of games saved to the games.list file including file paths and task names.\n"
    echo -e "\e[3mplay -f [PATTERN]\e[0m"
    echo -e "Search for a matching PATTERN in the games.list file and list results excluding file paths.\n"
    echo -e "\e[3mplay -fv [PATTERN]\e[0m"
    echo -e "Search for a matching PATTERN in the games.list file and list results including file paths and task names.\n"
    echo -e "\e[3mplay -a [COMMAND] [TITLE] [PATH] [TASK]\e[0m"
    echo -e "Add a game to the list that is located at the PATH on the Windows machine, is started by using the COMMAND parameter, and can be easily referenced in the -l functionality by the game's formal TITLE, which shows up in task manager as TASK (typically a \".exe\" file).\n"
    echo -e "\tFilepath shortcuts for PATH"
    echo -e "\t---------------------------"
    printf  "\t%-10s %-1s %-50s\n" "%STEAM%" "=" "$STEAM"
    printf  "\t%-10s %-1s %-50s\n" "%DESKTOP%" "=" "$DESKTOP"
    printf  "\t%-10s %-1s %-50s\n\n" "%APPDATA%" "=" "$APPDATA"
    echo -e "\e[3mplay -r [PATTERN]\e[0m"
    echo -e "Search for a matching PATTERN in the games.list file and remove it from the games.list file. This will generate a games.list.bak file, which is the games.list file before the specified program is removed.\n"
    echo -e "\e[3mplay -?\e[0m OR \e[3mplay --help\e[0m"
    echo -e "Print this help text.\n"
}
function list_games()
{
        for (( IFS=" " i=0; i<$ctr; i++ ))
    do
        print_normal_record "${short[i]}" "${long[i]}"
        done
    echo
}
function list_games_verbose()
{
        for (( IFS=" " i=0; i<$ctr; i++ ))
    do
        #Remove/replace backslash, quote marks, and path shortcuts from path and print result in table
        pathf="${path[i]//\\/}"
        format_pathf
            print_verbose_record "${short[i]}" "${long[i]}" "$pathf" "${servicename[i]}"
        done
    echo
}
function run_game()
{
    isRunning=0

    echo "Changing monitor input and launching winrun with parameters \"start $1\""
    sudo chmon $display
    winrun "start $1"

    while [[ 1 ]]
    do
        #Wait for program to actually show up in Task Manager (accounts for user messing around in launcher
        if [ -n $(winrun "tasklist /fi \"SESSIONNAME eq CONSOLE\" /fo list" | grep -i $task) ] && [!isRunning]; then
            isRunning=1

        #Switch monitor inputs when program is no longer running
        elif [ -z $(winrun "tasklist /fi \"SESSIONNAME eq CONSOLE\" /fo list" | grep -i $task) ] && [isRunning]; then
            echo "No longer seeing $task running on Window system, changing display input."
            winrun "run $ControlMyMonitor /SetValue \\\\.\DISPLAY1\Monitor0 60 4"
            break;
        fi
    done
}

#Load games from games.list file
if [ -f $games ]
then

    #Read games list
    while IFS= read -r line
    do
        short[$ctr]=$(echo $line | awk -F, '{print $1}')
        long[$ctr]=$(echo $line | awk -F, '{print $2}')
        path[$ctr]=$(echo $line | awk -F, '{print $3}')
        servicename[$ctr]=$(echo $line | awk -F, '{print $4}')
        ((ctr++))
    done < $games
else
    #Make games list file if it does not exist
    echo $games file  not found, creating!
    touch $games
fi

#Check for script arguments
while [ "$#" != "" ]
do
    case "$1" in
        -l)#List games

            if [ "$2" ]
            then
                echo -e "Detected arguments after \""$1"\". Ignoring, as these are not used...\n"
            fi

            get_normal_header_spacing
            print_normal_header
            list_games | sort
            break;;

        -lv)#Display games list with more info
            
            if [ "$2" ]
            then
                echo -e "Detected arguments after \""$1"\". Ignoring, as these are not used...\n"
            fi

            get_verbose_header_spacing
                        print_verbose_header
            list_games_verbose | sort
                        break;;

        -f)#Find for parameter and display

            if [ ! "$2" ]
            then
                echo "Missing search parameter!" >&2
                break
            fi
            if [ "$3" ]
            then
                echo -e "Detected arguments after \""$2"\". Ignoring, as these are not used...\n"
            fi

            get_normal_header_spacing
            print_normal_header
            echo
            list_games | grep -i "$2" | sort
            break;;

        -fv)#Find for parameter and display with more info

            if [ ! "$2" ]
            then
                echo "Missing search parameter!" >&2
                break
            fi
            if [ "$3" ]
            then
                echo -e "Detected arguments after \""$2"\". Ignoring, as these are not used...\n"
            fi

            get_verbose_header_spacing
            print_verbose_header
            echo
            list_games_verbose | grep -i "$2" | sort
            break;;

        -a)#Add game to list

            if [ ! "$2" ]
            then
                echo "Missing program command string!" >&2
                break

                elif [ ! "$3" ]
                then
                    echo "Missing program title!" >&2
                    break

                    elif [ ! "$4" ]
                    then
                        echo "Missing program path!" >&2
                        break

                        elif [ ! "$5" ]
                        then
                            echo "Missing program task name!" >&2
                            break
            fi
            if [ "$6" ]
            then
                echo -e "Detected arguments after \""$5"\". Ignoring, as these are not used...\n"
            fi

            #Check for duplicate entry
            check_for_duplicate "$2"
            check_for_duplicate "$3"
            check_for_duplicate "$4"

            #If no duplicate is found, add entry
            if [ ! $match ]
            then
                pathf="${4//\\//}"
                echo ""$2","$3","$pathf","$5"" >> "$games"
                echo "Added the following record:"
                echo
                print_verbose_header
                echo
                format_pathf
                    print_verbose_record "$2" "$3" "$pathf" "$5"
            fi

            break;;

        -r)#Attempt to remove game from list

            if [ ! "$2" ]
            then
                echo "Missing search parameter!" >&2
                break
            fi
            if [ "$3" ]
            then
                echo -e "Detected arguments after \""$2"\". Ignoring, as these are not used...\n"
            fi

            rctr=0
            while IFS= read -r line
            do
                if [[ -n $(echo $line | grep "$2") ]]
                then
                    match=1

                    get_record $rctr "$line"
                    echo "Discovered the following matching record:"
                    echo
                    print_verbose_header
                    echo
                    pathf="${path[$rctr]}"
                    format_pathf
                        print_verbose_record "${short[$rctr]}" "${long[$rctr]}" "$pathf" "${servicename[$rctr]}"
                    echo                    

                    while [[ $answer != "y" && $answer != "n" ]]
                    do
                        echo -n "Remove this record? (y/n) "
                        read -r answer </dev/tty
                    done

                    if [ $answer == "n" ]
                    then
                        echo "$games not changed."
                        break
                    fi

                    echo
                    echo "Removed the following record:"
                    print_verbose_header
                    echo
                    print_verbose_record "${short[$rctr]}" "${long[$rctr]}" "$pathf" "${servicename[$rctr]}"

                    #Shift all entries beyond the removed entry down one
                    for (( i=$rctr; i<ctr; i++ ))
                    do
                        short[i]=${short[i+1]}                      
                        long[i]=${long[i+1]}
                        path[i]=${path[i+1]}
                        servicename[i]=${servicename[i+1]}
                    done

                    #Rebuild games.list
                    cp $games $games.bak
                    rm $games
                    for (( i=0; i<(ctr-1); i++ ))
                    do
                        echo "${short[i]}","${long[i]}","${path[i]}","${servicename[i]}" >> $games
                    done

                    break
                fi

                ((rctr++))
            done < $games


            if [ ! $match ]
            then
                echo -e "Did not find match for \""$2"\" in \"$games\""
            fi
            break;;

        -? | --help)#Print help text

            if [ "$2" ]
            then
                echo -e "Detected arguments after \""$1"\". Ignoring, as these are not used...\n"
            fi

            print_help
            break;;

        *)#Search for matching game

            if [ ! "$1" ]
            then
                print_help
                break;
            fi

            if [ "$2" ]
            then
                echo -e "Detected arguments after \""$1"\". Ignoring, as these are not used...\n"
            fi

            echo -n "Checking if \""$1"\" matches a known game in games.list... "
            shopt -s nocasematch
            for (( i=0; i<$ctr; i++ ))
            do
                if [[ "$1" == "${short[i]}" ]]; then
                    path[i]="${path[i]//%STEAM%/$STEAM}"
                    path[i]="${path[i]//%DESKTOP%/$DESKTOP}"
                    path[i]="${path[i]//%APPDATA%/$APPDATA}"
                    prgm="${path[i]}"
                    task="${servicename[i]}"
                    break
                fi
            done
            shopt -u nocasematch

            #If there was a match, run program. Otherwise, run first arg as is.
            if [ "$prgm" != "" ]
            then
                echo "Found!"
                run_game "$prgm"
            else
                echo "Not found!"
                run_game "$1"
            fi

            break;;
    esac
    shift

#LANG=$oLang LC_ALL=$oLcAll

done
linux bash
  • 1 1 个回答
  • 922 Views

1 个回答

  • Voted
  1. Best Answer
    user1686
    2020-11-02T17:09:09+08:002020-11-02T17:09:09+08:00

    Bash's printf field widths work in bytes, not in characters. Because ý is stored as two bytes in the UTF-8 encoding, printf will think it's two columns wide – regardless of your locale setting.

    (Yes, that is inconsistent with string operations like ${#var}, most likely because the printf command is just a direct passthrough to the C printf() function which is always byte-oriented and bash has no say in how it works.)

    However, if printf worked with characters, then LC_CTYPE=C would be counterproductive – you would want the two bytes c3 bd to be treated as representing a single character, just as they're drawn in a single cell, and therefore you would want to keep LC_CTYPE set to something.UTF-8. (If bash thinks a string is wider than it's actually displayed, then it will add less padding, and so the column will appear to be narrower than it should be, just like in your situation.)

    I would recommend using the column tool that is part of most Linux systems – it does understand UTF-8 input, and it would save you from needing to calculate the column widths in general. In fact it would even work with characters that are more than 1 cell wide – for example:

    (
        printf '%s\t%s\t%s\t%s\n' "iceland" "Mýrdalssandur Iceland" "steam://rungameid/1248990" "iceland-Win64-Shipping.exe"
        printf '%s\t%s\t%s\t%s\n' "il2" "IL 2 Sturmovik: 1946" "steam://rungameid/15320" "il2fb.exe"
        printf '%s\t%s\t%s\t%s\n' "ksp" "Kerbal Sp?ce Pr?gram" "steam://rungameid/220200" "KSP_x64.exe"
        printf '%s\t%s\t%s\t%s\n' "ffxiv" "ファイナルファンタジーXIV(FF14)" "steam://rungameid/39210" "ffxiv.exe"
    ) | column -s $'\t' -t -N SHORT,LONG,URL,EXE -W LONG -o ' │ '
    

    The fancy options like -N or -o require at least version 2.30 of the util-linux package, but the basic column -t -s ... will still work with older versions.

    But if you do not want to use any external tools, then you'll need to manually pad the strings – making use of the ${#var} expansion which tells you the number of characters (although not the number of terminal cells, and therefore not as good as column):

    printf '%s%*s | %s%*s | %s%*s | %s%*s\n' \
      "$short" $((maxshort - ${#short})) "" \
      "$long"  $((maxlong - ${#long}))   "" \
      "$url"   $((maxurl - ${#url}))     "" \
      "$exe"   $((maxexe - ${#exe}))     "" ;
    

    Note: In both cases above, whether you use 'column' or bash's ${#var}, you need the data to be interpreted as UTF-8 to get the correct character count, and therefore you must not set LANG or LC_ALL to "C".

    • 15

相关问题

  • Notify-发送窗口下出现的通知

  • 以 root 身份运行 docker 容器

  • 如何在域和 Linux 活动目录中启用指纹传感器

  • 如何在CentOS 7 中将Ctrl+C 永久更改为Ctrl+K?

  • 如何从 WSL 打开 office 文件

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何减少“vmmem”进程的消耗?

    • 11 个回答
  • Marko Smith

    从 Microsoft Stream 下载视频

    • 4 个回答
  • Marko Smith

    Google Chrome DevTools 无法解析 SourceMap:chrome-extension

    • 6 个回答
  • Marko Smith

    Windows 照片查看器因为内存不足而无法运行?

    • 5 个回答
  • Marko Smith

    支持结束后如何激活 WindowsXP?

    • 6 个回答
  • Marko Smith

    远程桌面间歇性冻结

    • 7 个回答
  • Marko Smith

    子网掩码 /32 是什么意思?

    • 6 个回答
  • Marko Smith

    鼠标指针在 Windows 中按下的箭头键上移动?

    • 1 个回答
  • Marko Smith

    VirtualBox 无法以 VERR_NEM_VM_CREATE_FAILED 启动

    • 8 个回答
  • Marko Smith

    应用程序不会出现在 MacBook 的摄像头和麦克风隐私设置中

    • 5 个回答
  • Martin Hope
    CiaranWelsh 如何减少“vmmem”进程的消耗? 2020-06-10 02:06:58 +0800 CST
  • Martin Hope
    Jim Windows 10 搜索未加载,显示空白窗口 2020-02-06 03:28:26 +0800 CST
  • Martin Hope
    v15 为什么通过电缆(同轴电缆)的千兆位/秒 Internet 连接不能像光纤一样提供对称速度? 2020-01-25 08:53:31 +0800 CST
  • Martin Hope
    fixer1234 “HTTPS Everywhere”仍然相关吗? 2019-10-27 18:06:25 +0800 CST
  • Martin Hope
    andre_ss6 远程桌面间歇性冻结 2019-09-11 12:56:40 +0800 CST
  • Martin Hope
    Riley Carney 为什么在 URL 后面加一个点会删除登录信息? 2019-08-06 10:59:24 +0800 CST
  • Martin Hope
    zdimension 鼠标指针在 Windows 中按下的箭头键上移动? 2019-08-04 06:39:57 +0800 CST
  • Martin Hope
    jonsca 我所有的 Firefox 附加组件突然被禁用了,我该如何重新启用它们? 2019-05-04 17:58:52 +0800 CST
  • Martin Hope
    MCK 是否可以使用文本创建二维码? 2019-04-02 06:32:14 +0800 CST
  • Martin Hope
    SoniEx2 更改 git init 默认分支名称 2019-04-01 06:16:56 +0800 CST

热门标签

windows-10 linux windows microsoft-excel networking ubuntu worksheet-function bash command-line hard-drive

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve