POSIX shell funkce: tisk argumenty jako TSV záznam

0

Otázka

Píšu POSIX shell funkce, která vytiskne své argumenty jako TSV záznam.
Každý argument je, utekl s následujícími pravidly:

  • \n pro newline
  • \t pro kartu
  • \r pro návrat vozíku
  • \\ pro zpětné lomítko

Zde je funkce:

#!/bin/sh

tsv_print() {
    rec=
    for str in "$@"
    do
        esc=
        i=${#str}
        until [ $i -eq 0 ]
        do
            end="${str#?}"
            chr="${str%"$end"}"

            case $chr in
            "$__TAB__") chr='\t' ;;
            "$__LF__") chr='\n' ;;
            "$__CR__") chr='\r' ;;
            \\) chr='\\' ;;
            esac

            esc="$esc$chr"
            str="${end}"
            i=$((i-1))
        done
        rec="$rec${rec:+"$__TAB__"}$esc"
    done
#   echo "$rec"
    printf '%s\n' "$rec"
}

S — bolestivé číst v kódu — znaky uloženy předem takto:

__TAB__=$(printf '\t')
__CR__=$(printf '\r')
__LF__="
"

Chtěl bych vědět:

  1. Proč není můj kód escape znaky?

    edit: Jako @GordonDavisson poukázal na to, echo byl viník!! Pomocí printf se zdá být pouze přenosné způsob, s náklady na případný fork.

  2. Je tam lepší, POSIX kompatibilní, způsob, jak to udělat? awk a sed nezdá vhodné pro práci...

  3. Jak by vám to un-vyhnout?

    edit: Jako @KamilCuk publikováno v jeho odpověď, printf '%b' bude stačit; TSV záznam má správný formát.


postscript

Nakonec, funkce nebylo zapotřebí, protože vstupní neměl obsahovat jakýkoliv znak uniknout. To znamená, že vstupní formát nebyl tak přímočarý, jak převést. To byla HVĚZDA Souboru s různým počtem sloupců na řádek (omezení linky do 80 znaků) a obsahující uvedené řetězce...

vstup:

...
loop_ 
 _refl_0201 _refl_0012 _refl_2003 _refl_1600 _refl_1304 _refl_1305 _refl_1800
 _refl_1801 _refl_1802 _refl_1803 _refl_1804 _refl_1805 _refl_1806 _refl_1701
 _refl_1700 _refl_1202
'0 0 6' .147364 Z000020c1 .41 1 78.45 3.501 35.2221 -35.2221 0 -1.6055 -3.0963
-36.7288 -5.0964 39.3109 5.909983 '0 0 12' .294551 Z000010c1 .9 1 48.44 2.3805
39.910008 39.9101 .268379-04 1.75598 3.09745 41.6656 3.09809 47.8384 0 .939517
...

výstup (separátory jsou záložky):

_refl_0201 _refl_0012 _refl_2003 _refl_1600 _refl_1304 _refl_1305 _refl_1800 _refl_1801 _refl_1802 _refl_1803 _refl_1804 _refl_1805 _refl_1806 _refl_1701 _refl_1700 _refl_1202
'0 0 8' .147364 Z000020c1 .41 1 78.45 3.501 35.2221 -35.2221 0 -1.6055 -3.0963 -36.7288 -5.0964 39.3109 5.909983
'0 0 14' .294551 Z000010c1 .9 1 48.44 2.3805 39.910008 39.9101 .268379-04 1.75598 3.09745 41.6656 3.09809 47.8384 0.939517
...
escaping posix sh
2021-11-20 18:22:21
2
1

Je tam lepší, POSIX kompatibilní, způsob, jak to udělat?

Nemyslím si, že vaše metoda je v pořádku, a bude neskutečně pomalé.

Pokud "lepší" je rychlost, můžete vždy napsat POSIX-kompatibilní C programu. (Ale opravdu, můžete jen kompilace GNU sed a pak sed -z to).

Chtěl bych jít s awk -v FS='' -v RS='' '{ gsub(/\\/, "\\\\"); gsub("\r", "\\r"); gsub(/\t/, "\\t"); gsub(/\n/, "\\n")} 1'i busybox awk zpracovává se, že, a napsat celou věc s awk.

Jak by vám to un-vyhnout?

printf "%b"


__LF__="
"
__TAB__=$(printf '\t')
__CR__=$(printf '\r')
2021-11-20 22:30:54

Nemusíte printf pro nový řádek. Stačí vložit nový řádek v řetězec v uvozovkách.
chepner

Mohu napsat C program, pokud je kompilátor k dispozici na počítači, ale To nemusí být nutné, pokud údaje pro převod není příliš velký
Fravadona

No, můžete cross-kompilovat lokálně a pak ji zkopírovat. TBH, pokud jdete tímto způsobem, cross-kompilace busybox, a pak budete mít všechny nástroje, a pak to všechno zapsat v awk.
KamilCuk

Vzhledem k inout dat, musel jsem se napsat program v jazyce awk
Fravadona
1

Můžete to udělat pomocí sed ale existuje několik triků, které jsou potřebné k práci.

Musíte uniknout "\" první, takže zpětná lomítka před "\t", "\n" a "\r" nebude se utekl.

sed funguje na řádek, ale můžete si ho objednat přidat "\n" na konci každého řádku a odstranit znaky konce řádku s samostatný příkaz.

sed dárky na poslední řádek řetězec stejné bez ohledu na to, jestli to skončí s "\n" nebo ne, ale dodal,'. ' na konci jeho vkládání a mazání 3 poslední znaky z výstupu dělá trik.

Toto řešení je mnohem rychlejší než shell smyčky. (V podstatě, všechno je rychlejší, než shell.)

tsv_print() {
    is_first_arg='yes'
    while [ $# -ne 0 ]
    do
        test "$is_first_arg" = 'yes' && is_first_arg='no' || printf '\t'
        printf '%s.' "$1" \
        | sed -e 's/\\/\\\\/g' \
            -e 's/\t/\\t/g' \
            -e 's/$/\\n/' \
            -e 's/\r/\\r/g' \
        | tr -d '\n' \
        | head -c -3
        shift
    done
}

Btw, existují jednodušší způsoby, jak definovat konstanty.

# StackOverflow replaces the characters but it works in a file.
__TAB__='   ' # insert <tab> here
__LF__='
' # insert \n here
__CR__='
' #insert \r here

...nebo

# This one works fine when copied from here.
__TAB__="$(printf '\t')"
__LF__='
'
__CR__="$(printf '\r')"
2021-11-20 21:20:31

Ne IFS triky jsou nezbytné pro vaše poslední příklad. Ani karty, ani návraty budou odstraněny z výstupu printf tím, že příkaz substituce.
chepner

(Ve skutečnosti, jsou globálně, nastavení hodnoty IFS v obou případech; úkoly sami sebe neberte doporučit úkoly, a printf nevypadá to na hodnotu IFS v jeho prostředí, mimochodem.)
chepner

@chepner zdá se, Že funguje dobře i bez IFS na mém systému. Nevím, proč jsem si myslel, že by pás "\r", i když jsem zmatená o znak tabulátoru. Není default hodnota IFS "\t\n"?
NO_NAME

Žádné slovo-dělení se aplikuje na pravé straně přiřazení. To je příkaz substituce sám, že pásy koncové řádky, nezávislý na aktuální hodnotu IFS.
chepner

V jiných jazycích

Tato stránka je v jiných jazycích

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................