Polecenie takie jak `column -t`, które zamiast tego utrzymuje separatory na wyjściu

17

Edytuję prosty stół. Chciałbym mieć to ładnie sformatowane. Chociaż mogę użyć tbl, latexlub podobnego, wydaje się to przesadą - zwykły tekst jest naprawdę wystarczający. Ponieważ jest to proste, równie dobrze mogę mieć źródło jako wynik. Więc źródło też powinno dobrze wyglądać. Wydaje się, że powinno to być idealne zadanie column -s '|' -t- znajduje separatory i automatycznie wstawia spacje, aby wyrównać je zgodnie z maksymalną szerokością w każdej kolumnie. Niestety usuwa separatory, więc nie mogę go ponownie uruchomić po dalszej edycji. Czy jest jakieś dobre narzędzie do przetwarzania tekstu, które potrafi to robić idempotentnie, aby jego dane wyjściowe służyły jako dane wejściowe? Czy też muszę pisać własne?

EDYCJA: oto przykład tego, czego chcę:

foo |   bar | baz
abc def | 12 | 23456

powinno stać się

foo     | bar | baz
abc def | 12  | 3456

Kiedy ' 'jest zarówno separatorem, jak i przekładką, column -tdziała dobrze. Ale w moich przedmiotach są spacje, więc nie mogę tego użyć. Oddzielenie elementów dystansowych od separatorów komplikuje sprawę. Myślę, że warto traktować je jako znaki oddzielające obok separatorów, ale to nie column -s '|' -ttak (choć oczywiście obecne zachowanie jest również przydatne).

wnoise
źródło
Możesz użyć emacs org-mode. Obsługa tabeli jest naprawdę niesamowita, zapewniając funkcjonalność podobną do arkusza kalkulacyjnego.
vschum
Nie tak ogólne, jak uważałem, że byłoby rozsądne, ale istnieje program pythonowy specjalnie dla tabel Markdown na leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate .
wnoise
To problem, na który wpadam co najmniej co dwa tygodnie. Jedynym realnym rozwiązaniem do omijania printfholokaustu za każdym razem, które do tej pory znalazłem, jest dodanie do danych unikalnego char (podobnego @), a ... | column -s@ -tnastępnie użycie .
sjas,

Odpowiedzi:

17

Nie jestem pewien, czy dobrze rozumiem, na czym polega twój problem. Ale czy można to rozwiązać, dodając dodatkowy separator czasowy? dlatego możesz użyć drugiego separatora do oznaczenia separacji, nie zmieniając oryginalnego separatora.

Zobacz ten przykład, w którym dodaję „@” do każdego z „|” więc wejście polecenia kolumny brzmiałoby „xxx @ | rrrr”. Kolumna przetworzy „@” zachowując „|” nietknięty:

~$ echo "foo | this is some text | bar" | sed 's/|/@|/g'  | column -s '@' -t
foo   | this is some text   | bar
hmontoliu
źródło
Sprytny. Prawie robi to, co chcę i faktycznie robi to, o co prosiłem - pozostawia separatory w środku. Chcę również, aby przestrzenie obok prawdziwych separatorów mogły być regulowane w dół, a nie tylko w górę, jak tutaj.
wnoise
@wnoise: użyj sed 's/ *| */@| /g'zamiast
Stéphane Gimenez
@ Stéphane Gimenez: I dodawanie sed 's/ |/|/g'po columnpoprawkach dodanych dodatkowych spacji. Mamy teraz rozwiązanie, które działa dla mnie wystarczająco dobrze. (Choć byłoby miło, gdyby nie zależało to od takiej dodatkowej postaci. Co, jeśli nie jest dostępna?)
wnoise
3
@wnoise: Zamiast @ możesz użyć czegoś, co zwykle nie pojawia się w tekście, na przykład niskiej wartości ASCII, np. $ '\ x01' ... (ale nie $ '\ x00') ...
Peter.O
6

To nie było dostępne, gdy zadałeś pytanie, ale od wersji 2.23 column od util-linuxpozwala wybrać separator wyjściowy za pośrednictwem

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

Więc po prostu uruchom:

 column -s '|' -o '|' -t infile
don_crissti
źródło
Zauważ, że util-linuxwersja nie jest dostępna w systemie Ubuntu 18.04 (i prawdopodobnie w innych dystrybucjach pochodzących z Debain) w momencie pisania. bsdmainutilsDostępna jest tylko wersja. bsdmainutilsWersja nie obsługuje formatowanie wyjścia.
htaccess
5

Oto skrypt bash. Nie używa „kolumny -t”, a separator jest obsługiwany dokładnie tak, jak IFS, ponieważ jest to IFS (lub przynajmniej wewnętrzna wersja IFS awk) ... Domyślnym ogranicznikiem jest $ '\ t'

Ten skrypt całkowicie wypełnia skrajnie prawe pole.
„kolumna” tego nie robi.
Wypełniając wszystkie kolumny, skrypt ten można
łatwo zmodyfikować, aby również utworzyć ramkę tabeli.

Uwaga. Plik wejściowy musi zostać przetworzony dwukrotnie
(„kolumna” również musiałaby to zrobić)
. Pierwsze przejście to uzyskanie maksymalnej szerokości kolumny.
Drugim krokiem jest rozwinięcie pól (na kolumnę)

Dodano kilka opcji i naprawiono rażący błąd (zmiana nazw zmiennych :(

  • -l Lewe przycięcie białych znaków dowolnych wciętych pól
  • -r Prawe przycięcie białych znaków jest szersze niż najszerszy tekst (dla kolumny)
  • -b Zarówno -l, jak i -r
  • -L Dodano lewy ogranicznik wyjściowy
  • -R Dodano prawy ogranicznik wyjściowy
  • -B Zarówno -L, jak i -R
  • -S Wybierz separator wyjściowy

#!/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
Peter.O
źródło
Ładnie wykonane. Oczywiście liczyłem na coś, co tak naprawdę nie wymagałoby napisania nowego programu.
wnoise,
2

Spójrz na wtyczkę vima o nazwie Tabularize

:Tabularize /<delim>
Amos Folarin
źródło
1

Jest to dwuprzebiegowe ulepszenie odpowiedzi hmontoliu , która pozwala uniknąć konieczności sztywnego kodowania separatora poprzez odgadywanie go na podstawie danych wejściowych.

  1. przeanalizuj dane wejściowe dla pojedynczych znaków niealfanumerycznych otoczonych spacjami, posortuj je według najbardziej popularnych i załóż, że najczęstszym znakiem jest separator, do którego jest przypisany $d.
  2. postępuj mniej więcej tak, jak w odpowiedzi hmonoliu , ale użyj ASCII NULL jako wypełnienia zamiast zamiast @, zgodnie z komentarzem PeterO .

Kod jest funkcją, która akceptuje nazwę pliku lub dane wejściowe z 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 ;
}

Wyjście algn foo(lub też algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456
agc
źródło
Patrząc na to rok później, wygląda na to, że wywołanie STDIN nie może i nie powinno działać, ponieważ zużywa STDIN dwa razy. Testowanie z dużymi plikami (około 80 milionów linii) wskazuje, że najwyraźniej działa poprawnie. Hmm ...
agc
0

Użyto pomysłu hmontoliu do wdrożenia prostej komendy:

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

Komentarz:

  • ${1:-,}- jest pierwszym argumentem z ,domyślnie
  • pierwszy sedwstawia symbol pośredni ( $intermdrugi argument lub ~domyślnie)
  • następnie columnzastępuje symbol pośredni spacjami, które wyrównują
  • drugi sedczyści zbędne spacje po columnrozkazie

Przykład użycia:

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

a   : bb: cccc
aaaa: b : cc

Jest również dobry, ponieważ jest idempotentny: możesz zastosować go kilka razy i uzyskać ten sam rezultat (na przykład podczas edycji w vimie i wyrównywania).

Alexey
źródło