Команда типа `column -t`, которая вместо этого сохраняет разделители в выводе


17

Я редактирую простую таблицу. Я хотел бы, чтобы это было красиво отформатировано. Хотя я мог бы использовать tbl, latexили подобное, это кажется излишним - простого текста действительно достаточно. Поскольку это просто, я мог бы также иметь источник, чтобы быть выходом. Так что источник тоже должен хорошо выглядеть. Кажется, что это должно быть идеальной работой column -s '|' -t- он находит разделители и автоматически вставляет пробелы для выравнивания в соответствии с максимальной шириной в каждом столбце. К сожалению, он удаляет разделители, поэтому я не могу перезапустить его после дальнейшего редактирования. Есть ли какой-нибудь хороший инструмент для обработки текста, который может сделать это идемпотентно, чтобы его вывод служил вводом? Или мне нужно написать свое?

РЕДАКТИРОВАТЬ: вот пример того, что я хочу:

foo |   bar | baz
abc def | 12 | 23456

должен стать

foo     | bar | baz
abc def | 12  | 3456

Когда ' 'есть разделитель и разделитель, column -tработает хорошо. Но в моих предметах есть пробелы, поэтому я не могу ими воспользоваться. Наличие разделителей, отличных от разделителей, усложняет ситуацию. Я думаю, что полезно использовать их как символы-разделители, когда они находятся рядом с разделителями, но это не то, что column -s '|' -tнужно (хотя, очевидно, текущее поведение также полезно).


Вы можете использовать emacs org-mode. Поддержка таблиц на самом деле довольно удивительна, обеспечивая функциональность, подобную электронным таблицам.
Вщум

Не так широко, как мне казалось бы разумным, но есть программа на python, специально предназначенная для таблиц уценки, по адресу leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate .
Внуаз

Это проблема, с которой я сталкиваюсь, по крайней мере, каждые две недели. Единственное жизнеспособное решение для обхода printfХолокоста каждый раз, которое я нашел до сих пор, - это добавление в данные уникального символа (например @) и ... | column -s@ -tпоследующее использование .
Sjas

Ответы:


17

Не уверен, правильно ли я понимаю, в чем твоя проблема. Но можно ли решить, добавив дополнительный временный разделитель? следовательно, вы можете использовать второй разделитель, чтобы отметить разделение, оставив исходный разделитель без изменений.

Посмотрите этот пример, где я добавляю «@» к каждому из «|» поэтому ввод команды столбца будет «ххх @ | гггг». Столбец будет обрабатывать "@", сохраняя "|" нетронутым:

~$ echo "foo | this is some text | bar" | sed 's/|/@|/g'  | column -s '@' -t
foo   | this is some text   | bar

Умная. Почти делает то, что я хочу, и фактически делает то, что я просил - оставляет разделители внутри. Я также хочу, чтобы пространства рядом с истинными разделителями можно было уменьшать, а не просто увеличивать, как здесь.
Внуаз

@wnoise: используйте sed 's/ *| */@| /g'вместо этого
Стефан Гименес

@ Стефан Гименес: И добавив sed 's/ |/|/g'после columnисправлений дополнительные пробелы добавлены. Теперь у нас есть решение, которое работает достаточно хорошо для меня. (Хотя было бы неплохо, если бы он не зависел от такого дополнительного персонажа, как этот. Что если он недоступен?)
wnoise

3
@wnoise: вместо @ вы можете использовать то, что обычно не отображается в тексте, например, низкое значение ASCII, например. $ '\ x01' ... (но не $ '\ x00') ...
Peter.O

6

Это было недоступно, когда вы задали вопрос, но по состоянию на v. 2.23 column из util-linuxпозволяет выбрать разделитель вывода с помощью

   -o, --output-separator string
          Specify the columns delimiter for table output (default is two spaces).

Так что просто запустите:

 column -s '|' -o '|' -t infile

Обратите внимание, что util-linuxверсия недоступна в Ubuntu 18.04 (и, возможно, в других дистрибутивах, производных от Debain) на момент написания этой статьи. bsdmainutilsДоступна только версия. bsdmainutilsВерсия не поддерживает выход форматирования.
htaccess

5

Вот скрипт bash. Он не использует 'column -t`, и разделитель обрабатывается точно так же, как и IFS, потому что это IFS (или, по крайней мере, внутренняя версия IFS в awk) ... Разделителем по умолчанию является $' \ t '

Этот скрипт полностью дополняет самое правое поле.
'column' не делает этого.
Разбив все столбцы, этот скрипт можно
легко изменить, чтобы создать рамку таблицы.

Заметка. Входной файл должен быть обработан дважды
(для этого также нужно будет сделать «column»)
. Первый шаг - получить максимальную ширину столбца.
Второй проход - расширение полей (на столбец).

Добавлены некоторые опции и исправлена ​​явная ошибка (переименование переменных :(

  • -l Левый урезать пробелы любых полей с отступом
  • -r Правая полоса пропускается шире, чем самый широкий текст (для столбца)
  • -b и -l и -r
  • -L Добавлен левый выходной разделитель
  • -R Добавлен правый выходной разделитель
  • -B Оба -L и -R
  • -S Выберите выходной разделитель

#!/bin/bash
#
#   script [-F sep] [file]
#
#   If file is not specified, stdin is read 
#    
# ARGS ######################################################################
l=;r=;L=;R=;O=;F=' ' # defaults
for ((i=1;i<=${#@};i++)) ;do
  case "$1" in
    -- ) shift 1;((i--));break ;;
    -l ) l="-l";shift 1;((i-=1)) ;;        #  left strip whitespace
    -r ) r="-r";shift 1;((i-=1)) ;;        # right strip whitespace
    -b ) l="-l";r="-r";shift 1;((i-=1)) ;; # strip  both -l and -r whitespace
    -L ) L="-L";shift 1;((i-=1)) ;;        #  Left output delimiter is added
    -R ) R="-R";shift 1;((i-=1)) ;;        # Right output delimiter is added
    -B ) L="-L";R="-R";shift 1;((i-=1)) ;; # output Both -L and -R delimiters
    -F ) F="$2";shift 2;((i-=2)) ;; # source separator
    -O ) O="$2";shift 2;((i-=2)) ;; # output  separator. Default = 1st char of -F 
    -* ) echo "ERROR: invalid option: $1" 1>&2; exit 1 ;;
     * ) break ;;
  esac
done
#
if  [[ -z "$1" ]] ;then # no filename, so read stdin
  f="$(mktemp)"
  ifs="$IFS"; IFS=$'\n'; set -f # Disable pathname expansion (globbing)
  while read -r line; do
    printf "%s\n" "$line" >>"$f"
  done
  IFS="$ifs"; set +f # re-enable pathname expansion (globbing)
else
  f="$1"
fi
[[ -f "$f" ]] || { echo "ERROR: Input file NOT found:" ;echo "$f" ;exit 2 ; }
[[ -z "$F" ]] && F=' '        # input Field Separator string
[[ -z "$O" ]] && O="$F"       # output Field Separator
                 O="${O:0:1}" #   use  single char only

# MAIN ######################################################################
max="$( # get max length of each field/column, and output them
  awk -vl="$l" -vr="$r" -vL="$L" -vR="$R" -vF="$F" -vO="$O" '
    BEGIN { if (F!="") FS=F }
    { for (i=1;i<=NF;i++) { 
        if (l=="-l") { sub("^[ \t]*","",$i) }
        if (r=="-r") { sub("[ \t]*$","",$i) }
        len=length($i); if (len>max[i]) { max[i]=len } 
        if (i>imax) { imax=i } 
      } 
    }
    END { for(i=1;i<=imax;i++) { printf("%s ",max[i]) } }
  ' "$f" 
)"

awk -vl="$l" -vr="$r" -vL="$L" -vR="$R" -vF="$F" -vO="$O" -v_max="$max" '
  BEGIN { if (F!="") FS=F; cols=split(_max,max," ") }
  { # Bring each field up to max len and output with delimiter
    printf("%s",L=="-L"?O:"")
    for(i=1;i<=cols;i++) { if (l=="-l") { sub("^[ \t]*","",$i) } 
                           if (r=="-r") { sub("[ \t]*$","",$i) }
      printf("%s%"(max[i]-length($i))"s%s",$i,"",i==cols?"":O) 
    } 
    printf("%s\n",R=="-R"?O:"")
  }
' "$f"

# END #######################################################################    
if  [[ -z "$1" ]] ;then # no filename, so stdin was used
  rm "$f"   # delete temp file
fi
exit

Красиво сделано. Конечно, я надеялся на что-то, что не потребовало бы написания новой программы.
Внуаз


1

Это два прохода твик на hmontoliu ответа «s , что позволяет избежать нуждающегося в жесткий код разделителя, угадывая его из входных данных.

  1. синтаксический анализ ввода для отдельных не алфавитно-цифровых символов, окруженных пробелами, сортировка их по наиболее распространенным признакам и предположение, что наиболее распространенным символом является разделитель, который назначен $d.
  2. продолжайте более или менее так же, как в ответе hmonoliu , но используйте ASCII NULL в качестве отступа, а не @, как в комментарии PeterO .

Код является функцией, которая принимает имя файла или ввод из STDIN :

algn() { 
    d="$(grep -ow '[^[:alnum:]]' "${1:-/dev/stdin}"  | \
         sort | uniq -c | sort -rn | sed -n '1s/.*\(.$\)/\1/p')" ;
    sed "s/ *$d */\x01$d /g" "${1:-/dev/stdin}"  | column -s $'\001' -t ;
}

Выход algn foo(или также algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456

Глядя на это год спустя, кажется, что вызов STDIN не может и не должен работать, потому что он использует STDIN дважды. Тестирование с большими файлами (около 80 миллионов строк) показывает, что оно работает правильно. Хм ...
АРУ

0

Использовал идею hmontoliu для реализации простой команды:

#! /bin/bash
delim="${1:-,}"
interm="${2:-\~}"
sed "s/$delim/$interm$delim/g" | column -t -s "$interm" | sed "s/  $delim/$delim/g"

Комментарий:

  • ${1:-,}- это первый аргумент ,по умолчанию
  • первый sedвставляет промежуточный символ ( $interm2-й аргумент или ~по умолчанию)
  • затем columnзаменяет промежуточный символ пробелами, которые выполняют выравнивание
  • вторая sedубирает лишние пробелы после columnкоманды

Пример использования:

$ echo "
a: bb: cccc
aaaa: b : cc
" | align :

a   : bb: cccc
aaaa: b : cc

Он также хорош тем, что он идемпотентен: вы можете применить его несколько раз и получить один и тот же результат (например, когда вы редактируете в vim и realign).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.