Как обнаружить и удалить отступ для переданного по трубопроводу текста - PullRequest
0 голосов
/ 03 января 2019

Я ищу способ убрать отступ в канале.Ниже приведено решение с использованием cut -c 9-, в котором предполагается, что отступ имеет ширину 8 символов.

Я ищу решение, которое может определить количество удаляемых пробелов.Это означает, что нужно пройти весь файл (piped), чтобы узнать минимальное количество пробелов (табуляций?), Используемых для отступа, а затем удалить их в каждой строке.

run.sh

help() {
    awk '
    /esac/{b=0}
    b
    /case "\$arg" in/{b=1}' \
    "$me" \
    | cut -c 9-
}

while [[ $# -ge 1 ]]
do
    arg="$1"
    shift
    case "$arg" in
        help|h|?|--help|-h|'-?')
            # Show this help
            help;;
    esac
done

$ ./run.sh --help

help|h|?|--help|-h|'-?')
    # Show this help
    help;;

Примечание: echo $' 4\n 2\n 3' | python3 -c 'import sys; import textwrap as tw; print(tw.dedent(sys.stdin.read()), end="")' работает, но я ожидаю, что есть лучший способ (я имею в виду, тот, который зависит не только отпрограммное обеспечение более распространенное, чем python. Может быть, awk? Я бы тоже не отказался от решения perl.

Note2: echo $' 4\n 2\n 3' | python -c 'import sys; import textwrap as tw; print tw.dedent(sys.stdin.read()),' также работает (Python 2.7.15rc1).

Ответы [ 4 ]

0 голосов
/ 04 января 2019

Вот (полу) очевидное решение временного файла.

#!/bin/sh

t=$(mktemp -t dedent.XXXXXXXXXX) || exit
trap 'rm -f $t' EXIT ERR
awk '{ n = match($0, /[^ ]/); if (NR == 1 || n<min) min = n }1
    END { exit min+1 }' >"$t"
cut -c $?- "$t"

Это, очевидно, не получается, если все строки имеют более 255 начальных пробельных символов, потому что тогда результат не будет вписываться в код выхода из Awk.

Это имеет то преимущество, что мы не ограничиваем себя доступной памятью.Вместо этого мы ограничиваемся доступным дисковым пространством.Недостатком является то, что диск может быть медленнее, но преимущество, связанное с отсутствием чтения больших файлов в память, ИМХО превзойдет это.

0 голосов
/ 03 января 2019

Предположим, у вас есть:

$ echo $'    4\n  2\n   3\n\ttab'
    4
  2
   3
    tab

Вы можете использовать утилиту Unix expand , чтобы расширить вкладки до пробелов.Затем выполните awk, чтобы подсчитать минимальное количество пробелов в строке:

$ echo $'    4\n  2\n   3\n\ttab' | 
expand | 
awk 'BEGIN{min_indent=9999999}
     {lines[++cnt]=$0
      match($0, /^[ ]*/)
      if(RLENGTH<min_indent) min_indent=RLENGTH
     }
     END{for (i=1;i<=cnt;i++) 
               print substr(lines[i], min_indent+1)}'
  4
2
 3
      tab
0 голосов
/ 04 января 2019

echo $'    4\n  2\n   3\n  \n   more spaces in  the    line\n  ...' | \
(text="$(cat)"; echo "$text" \
| cut -c "$(echo "$text" | sed 's/[^ ].*$//' | awk 'NR == 1 {a = length} length < a {a = length} END {print a + 1}')-"\
)

С пояснениями:

echo $'    4\n  2\n   3\n  \n   more spaces in  the    line\n  ...' | \
(
    text="$(cat)" # Obtain the input in a varibale
    echo "$text" | cut -c "$(
        # `cut` removes the n-1 first characters of each line of the input, where n is:
            echo "$text" | \
            sed 's/[^ ].*$//' | \
            awk 'NR == 1 || length < a {a = length} END {print a + 1}'
            # sed: keep only the initial spaces, remove the rest
            # awk:
            # At the first line `NR == 1`, get the length of the line `a = length`.
            # For any shorter line `a < length`, update the length `a = length`.
            # At the end of the piped input, print the shortest length + 1.
            # ... we add 1 because in `cut`, characters of the line are indexed at 1.
        )-"
)

Обновление:

Можно избежать нереста sed. Согласно комментарию tripleee, sed s/// может заменить awk's sub(). Вот еще более короткий вариант, использующий n = match(), как в ответе tripleee.

echo $'    4\n  2\n   3\n  \n   more spaces in  the    line\n  ...' | \
(
    text="$(cat)" # Obtain the input in a varibale
    echo "$text" | cut -c "$(
        # `cut` removes the a-1 first characters of each line of the input, where a is:
            echo "$text" | \
            awk '
                {n = match($0, /[^ ]/)}
                NR == 1 || n < a {a = n}
                END || a == 0 {print a + 1; exit 0}'
            # awk:
            # At every line, get the position of the first non-space character
            # At the first line `NR == 1`, copy that lenght to `a`.
            # For any line with less spaces than `a` (`n < a`) update `a`, (`a = n`).
            # At the end of the piped input, print a + 1.
            # a is then the minimum number of common leading spaces found in all lines.
            # ... we add 1 because in `cut`, characters of the line are indexed at 1.
            #
            # I'm not sure the whether the `a == 0 {...;  exit 0}` optimisation will let the "$text" be written to the script stdout yet (which is not desirable at all). Gotta test that when I get the time.

        )-"
)

По-видимому, это также можно сделать в Perl 6 с функцией my &f = *.indent(*);.

0 голосов
/ 03 января 2019

Следующее является чисто bash, без внешних инструментов или подстановок команд:

#!/usr/bin/env bash
all_lines=( )
min_spaces=9999 # start with something arbitrarily high
while IFS= read -r line; do
  all_lines+=( "$line" )
  if [[ ${line:0:$min_spaces} =~ ^[[:space:]]*$ ]]; then
    continue  # this line has at least as much whitespace as those preceding it
  fi
  # this line has *less* whitespace than those preceding it; we need to know how much.
  [[ $line =~ ^([[:space:]]*) ]]
  line_whitespace=${BASH_REMATCH[1]}
  min_spaces=${#line_whitespace}
done

for line in "${all_lines[@]}"; do
  printf '%s\n' "${line:$min_spaces}"
done

Его вывод:

  4
2
 3
...