5

在 C 或 Bash shell 中使用 printf,如何将两个字符串(字符数组)左右对齐到给定长度?

例如,如果字符串是“堆栈”和“溢出”,并且长度是 20 个字符,我希望打印

stack-------overflow

(为清楚起见,每个空格都显示为破折号)。

字符串的长度未知。如果总长度 + 1 超过给定的长度,是否可以从任一给定方向截断一个或两个字符串并在它们之间留一个空格?例如,如果长度是 10,我们可以得到其中的任何一个吗?

stack-over
stack-flow
s-overflow
k-overflow

我知道 printf("%10s", string) 将一个字符串向右对齐, printf("%-10s", string) 将一个字符串向左对齐,但找不到一种方法来证明 2 个字符串,或截断它们。

4

4 回答 4

3

这比电池长,但恕我直言,它可以更好地分开琴弦。它还在可能的情况下使用 printf 进行截断,仅针对第二个参数的左手截断回退到其他机制。

重击:

truncat () {
  local len=$1 a=$2 b=$3 len_a=${#2} len_b=${#3}
  if ((len <= 0)); then return
  elif ((${len_b} == 0)); then
    printf %-${len}.${len}s "$a"
  elif ((${len_a} == 0)); then
    printf %${len}.${len}s "${b: -$((len<len_b?len:len_b))}"
  elif ((len <= 2)); then
    printf %.${len}s "${a::1}${b: -1}"
  else
    local adj_a=$(((len_a*len+len_b-len_a)/(len_a+len_b)))
    local adj_b=$(((len_b*len+len_a-len_b-1)/(len_a+len_b)))
    printf "%-${adj_a}.${adj_a}s %${adj_b}.${adj_b}s" \
           "$a" \
           "${b: -$((len_b<adj_b?len_b:adj_b))}"
  fi
}

C:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void truncat(long len, const char* a, const char* b) {
  if (len <= 0) return;
  unsigned long long len_a = strlen(a);
  unsigned long long len_b = strlen(b);
  if (!len_b)
    printf("%-*.*s", (int)len, (int)len, a);
  else if (!len_a)
    printf("%*s", (int)len, b + (len < len_b ? len_b - len : 0));
  else if (len <= 2)
    printf("%.1s%.*s", a, (int)(len - 1), b + len_b - 1);
  else {
    unsigned long long adj_a = (len_a * len + len_b - len_a) / (len_a + len_b);
    unsigned long long adj_b = (len_b * len + len_a - len_b - 1) / (len_a + len_b);
    printf("%-*.*s %*s",
           (int)adj_a, (int)adj_a, a,
           (int)adj_b, b + (adj_b < len_b ? len_b - adj_b : 0));
  }
}

int main(int argc, char** argv) {
  truncat(atol(argv[1]), argv[2], argv[3]);
  return 0;
}

样本输出:

$ for i in {0..20}; do printf "%2d '%s'\n" $i "$(./truncat $i stack overflow)"; done
 0 ''
 1 's'
 2 'sw'
 3 's w'
 4 's ow'
 5 'st ow'
 6 'st low'
 7 'st flow'
 8 'sta flow'
 9 'sta rflow'
10 'stac rflow'
11 'stac erflow'
12 'stac verflow'
13 'stack verflow'
14 'stack overflow'
15 'stack  overflow'
16 'stack   overflow'
17 'stack    overflow'
18 'stack     overflow'
19 'stack      overflow'
20 'stack       overflow'

免责声明:算术可能会溢出,在这种情况下输出将是错误的(或者,如果您可以将 strlen(a)+strlen(b) 安排为正好 2^64 字节,则程序将 SIG_FPE)。如果有人关心,我可以为 adj_a 和 adj_b 计算提供解释。

于 2012-09-29T01:36:18.263 回答
2

您将不得不编写自己的函数来做到这一点。这是一个没有和一个容易截断的例子。对于截断,您需要定义一些规则来作为截断的基础。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void concat(const char* str1, const char* str2, char *result, int len)
{
   int str1len = strlen(str1);
   int str2len = strlen(str2);
   memset(result, ' ', len);
   if(!(str1len + str2len > len))
   {
      memcpy(result, str1, str1len);
      memcpy(result + len - str2len, str2, str2len);
      result[len] = '\0';
   }
   else
   {
      memcpy(result, str1, 1);
      memcpy(result + len - str2len, str2, str2len);
      result[len] = '\0';
   }
}

int main()
{
   char str1[] = "stack";
   char str2[] = "overflow";
   int len = 20;
   char* result = malloc(len + 1);
   int len2 = 10;
   char* result2 = malloc(len2 + 1);

   concat(str1, str2, result, len);

   printf("%s\n", result);

   concat(str1, str2, result2, len2);

   printf("%s\n", result2);

   free(result);
   free(result2);

   return 1;
}
于 2012-09-28T11:03:30.887 回答
1

我不认为你可以在一次操作中做到这一点,尤其是截断部分。请注意,在 C 中,例如printf("%2s", "hello");不会截断。字段宽度仅对填充目的很重要,而不是截断

您可能需要实现一个自定义函数来执行此操作,在 C 中是这样的:

void format_pair(char *out, size_t out_max, const char *s1, const char *s2);
于 2012-09-28T10:54:21.917 回答
1

在 Bash 中:

#!/bin/bash

function justify_two_strings {
    a=$1; shift;
    b=$1; shift;
    len=$1; shift;

    while [[ $(( ${#a} + ${#b} + 1 )) -gt $len ]]; do
        a=${a:0:-1}     # cut the last character of $a
        b=${b:1}        # cut the first character of $b
    done

    printf "%s%$(( len-${#a} ))s\n" $a $b
}

justify_two_strings 'stack' 'overflow' 20   # prints 'stack       overflow'
justify_two_strings 'stack' 'overflow' 10   # prints 'sta erflow'

while如果字符串不合适,您可能需要调整循环的核心以不同方式缩短字符串。

一些提示:

  • ${#a}给出长度$a
  • ${a:s:n}从索引开始返回n字符(从 0 开始)。如果省略,则返回直到字符串结尾的所有内容。$asn
于 2012-09-28T11:12:45.160 回答