#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
затем будет получен только первый результат из grepawk '{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 может быть таким мощным!