سؤال

Using printf in C or Bash shell, how can I left- and right-justify two strings (character arrays) to a given length?

E.g. if the strings are "stack" and "overflow", and the length is 20 characters, I wish to print

stack-------overflow

(for clarity, each space is shown as a dash).

The strings are of unknown length. If the total length + 1 exceeds the length given, is it possible to truncate one or both strings from either given direction and leave a space between them? E.g. if length is 10, can we get any of these?

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

I know that printf("%10s", string) justifies one string to the right and printf("%-10s", string) justifies one string to the left, but can't find a way to justify 2 strings, or to truncate them.

هل كانت مفيدة؟

المحلول

This is longer than battery's, but imho it splits the strings better. Also it uses printf for truncation where possible, falling back to other mechanisms only for left-hand truncation of the second argument.

Bash:

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;
}

Sample output:

$ 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'

Disclaimer: The arithmetic can overflow, in which case the output will be wrong (or, if you can arrange for strlen(a)+strlen(b) to be exactly 2^64 bytes, the program will SIG_FPE). I can provide an explanation for the adj_a and adj_b computations if anyone cares.

نصائح أخرى

You will have to write your own function to do that. Here is an example without and one with an easy truncation. For the truncation you will need to define some rules to base the truncation on.

#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;
}

I don't think you can do it in a single operation, especially the truncating part. Note that e.g. printf("%2s", "hello"); will not truncate, in C. The field width only matters for padding purposes, not truncating.

You will probably need to implement a custom function to do this, something like this in C:

void format_pair(char *out, size_t out_max, const char *s1, const char *s2);

In 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'

You might want to tweak the core of while loop to shorten the strings differently if they do not fit.

Some hints:

  • ${#a} gives the length of $a
  • ${a:s:n} returns n characters from $a starting at index s (0-based). If n is omitted, then it returns everything until the end of the string.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top