Скрипт Bash для управления двумя десятичными числами в строке

#bash #numbers #decimal

#bash #числа #десятичный

Вопрос:

Две строки данных, которые я обрабатываю, выглядят следующим образом.

 18 xy Pqr  -3879.65 xp9  a-kxp   Kap 97868.08 P8A jrh-uyjf iu-re
A4-18 usU Aqr 974.59  xpab9  Tb7k-p   ptx 1533.93  K-doe Uap-qe1
  

Основные характеристики:

  • Каждая строка содержит два десятичных числа.
  • Первое число может быть положительным или отрицательным, но второе число всегда положительное.

Я хочу перевернуть знак первого числа (положительный на отрицательный и наоборот) и удалить второе число.

Имея ограниченные навыки в bash, я написал следующий скрипт методом перебора. Это выглядит так неэлегантно! Спасибо за любые указания, которые участники форума могли бы предоставить, чтобы улучшить его.

 #replace whitespaces with "_" for easy 'sed'-ing
a=`echo "$this_line" | sed -e "s/ /_/g"`

#get head part with first decimal number
b=`echo $a | grep -Po '^.*?[-]?[0-9] [.][0-9] '`

#pick the decimal number from the head part
c=`echo $b | grep -Po '[-]?[0-9] [.][0-9] '`

#flip the sign of the decimal number from the head part
d=`echo -1 * $c | bc -l`

#delete decimail number from the head part
e=`echo "$b" | sed -e "s/$c$//"`

#put back head part with the decimal number sign flipped
f=`echo $e$d`

#get tail part with second decimal number
g=`echo "$a" | sed -e "s/^$b//"`

#pick the decimal number from the tail part
h=`echo $g | grep -Po '^.*?[-]?[0-9] [.][0-9] '`

#delete decimail number from the tail part
i=`echo "$g" | sed -e "s/^$h//"`

#join back without second decimal number and first decimal sign flipped
j=`echo $f"  "$i`

#replace back "_" by whitespace
modified_line=`echo "$j" | sed -e "s/_/ /g"`
  

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

1. Намерение не состоит в том, чтобы удалять символы между двумя числами. Они не находятся на четвертой или восьмой позициях; их позиции могут меняться от строки к строке.

2. Практически на любом языке программирования я бы сначала разделил строку пробелом, чтобы у вас был массив элементов. Затем выполните цикл по массиву и проверьте, выглядит ли элемент как число. Если это так, выполните операцию (изменение строки или замена элемента пустым значением). Наконец, воссоздайте строку на основе измененного массива. Недостатком (если вы делаете это наивно в bash) является то, что несколько пробелов в конечном итоге будут сжаты в один пробел. Вы можете избежать этого и в bash, но это становится немного сложнее. Лучше переключитесь на более подходящий язык.

3. Есть ли какая-нибудь простая команда для разделения строки на десятичные числа? В этом случае у меня будет пять частей: a) часть перед первым десятичным числом, b) первое десятичное число, c) часть между двумя десятичными числами, d) второе десятичное число и e) часть после второго десятичного числа.

4. Вы можете использовать регулярные выражения, чтобы разорвать строку на части. На справочной странице bash, раздел «compund commands», найдите ту часть, где объясняется [[выражение ]] . Это также объясняет использование регулярных выражений для ваших целей.

5. Спасибо. Так много нужно узнать!

Ответ №1:

Как насчет чего-то вроде

 awk '
{
    flipped=0
    for (i=1; i< NF; i  ) {
        if ($i ~ /-*[0-9] .[0-9] /) {
            $i = (!flipped  ) ? -$i : "";
        }
    }
    print
}
'
  

который выдает

 18 xy Pqr 3879.65 xp9 a-kxp Kap  P8A jrh-uyjf iu-re
A4-18 usU Aqr -974.59 xpab9 Tb7k-p ptx  K-doe Uap-qe1
  

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

1. Спасибо за идею. Я могу изменить его, чтобы адаптировать к реальным данным. В настоящее время он печатает, скажем, 5.00 как 5. Я новичок в awk. Существует ли решение проблемы sed / grep?

Ответ №2:

Awk — гораздо лучший инструмент при работе с числами с плавающей запятой, потому что Bash не имеет типа float:

 awk '{ printf ("%s %s %s %f %s %s %s %s %sn", $1, $2, $3, -$4, $5, $6, $7, $9, $10) }' input_file
  

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

1. Это сработает, если числа находятся в фиксированных позициях, например, в 4-м и 8-м. К сожалению, я имею дело с числами, которые могут отображаться в разных позициях.

Ответ №3:

 str="18 xy Pqr  -3879.65 xp9  a-kxp   Kap 97868.08 P8A jrh-uyjf iu-re"
echo "$str" 
  | grep -Eo -- '-?[0-9]*.[0-9] ' 
  | head -n 1 
  | awk '{print ($1 * -1)}'
  

Объяснение

  • grep -Eo -- '-?[0-9]*.[0-9] '' Найдет любую строку, которая начинается с необязательно дефиса, за которым следуют цифры, за которыми следует точка, за которой следуют цифры
  • head -n 1 затем будет получен только первый результат из grep
  • awk '{print ($1 *- 1)} затем напечатает первое число раз -1 (тем самым перевернув знак)

oneliner

 echo "$str" | grep -Eo -- '-?[0-9]*.[0-9] ' | head -n 1 | awk '{print ($1 * -1)}'
  

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

1. Этот код выдает перевернутый знак первого десятичного числа, что хорошо. Однако он не печатает другие части строки с удаленным вторым десятичным числом. Есть ли какой-либо способ напечатать несовпадающие части строки?

2. Какова цель двойного тире после -Eo в коде? Без них код не выполняется.

Ответ №4:

Более простое и понятное решение с использованием awk и замены переменных

 #get the string
string="A4-18 usU Aqr 974.59  xpab9  Tb7k-p   ptx 1533.93  K-doe Uap-qe1"
#get the first number
firstNumber=$(echo $string | awk '{print $4}')
#get the second number
secondNumber=$(echo $string | awk '{print $8}')
#calculate absolute value
absoluteValue=${firstNumber#-}
#replace string
echo $string | sed s/$firstNumber/$absoluteValue/ | sed s/$secondNumber//
  

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

1. В моих данных десятичные числа не всегда находятся в 4-м и 8-м местоположениях. Так что в целом это не сработает.

Ответ №5:

#!/bin/bash

msg=»18 xy Pqr K -261,90 xp9 P a-kxp 7873,57 Kap P8A jrh-uyjf»

printf ‘%sn’ «$msg»

#чтобы сохранить пробелы, замените их на «_»

a = echo "$msg" | sed -e "s/ /_/g"

#отобразить строку, чтобы показать точки разрыва

b = echo $a | sed -n "s/(^.*.[0-9].*_)([0-9]*.[0-9][0-9])(.*$)/1|2|3/p"

echo $b

#удалить второе десятичное число

b = echo $a | sed -n "s/(^.*.[0-9].*_)([0-9]*.[0-9][0-9])(.*$)/1 3/p"

#echo $b

#выберите единственное десятичное число, присутствующее в оставшейся строке

c = echo $b | grep -Po '[-]?[0-9] .[0-9] '

#изменить знак десятичного числа

d = echo -1 * $c | bc -l

#замените десятичное число обратно в строку новым знаком

e= echo ${b/$c/$d}

#заменить обратно «_» на пробел

modified_line= echo "$e" | sed -e "s/_/ /g"

printf ‘%sn’ «$modified_line»

выход 0

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

1. Я многому научился, выполняя вышеизложенное. Я никогда не знал, что sed может быть таким мощным!