Как найти последнее поле, используя «вырезать»

310

Без использования sedили awk, только cut , как я могу получить последнее поле , когда количество полей неизвестны или изменение с каждой строкой?

noobcoder
источник
8
Вы влюблены в cutкоманду :)? почему нет других команд Linux?
Джаеш Бхой
7
Без sedили awk: perl -pe 's/^.+\s+([^\s]+)$/$1/'.
Иордания
3
возможный дубликат того, как разбить строку в оболочке и получить последнее поле
Амир Али Акбари
4
@MestreLion Часто люди читают вопрос, чтобы найти решение проблемы. Это начинается с ложной предпосылки, которая cutподдерживает то, чего нет. Но я подумал, что это было полезно, так как заставляет читателя обдумывать код, за которым легче следовать. Я хотел быстрый, простой способ использовать cutбез необходимости использования нескольких синтаксисов для awk, grep, sedи т.д. , revчто сделал трюк; очень элегантно, и то, что я никогда не рассматривал (даже если неуклюжий для других ситуаций). Мне также понравилось читать другие подходы из других ответов.
Бежор
3
Пришла настоящая проблема: я хочу найти все различные расширения файлов в дереве исходных текстов, чтобы обновить файл .gitattributes. Так же find | cut -d. -f<last>как и естественная склонность
Studog

Ответы:

680

Вы можете попробовать что-то вроде этого:

echo 'maps.google.com' | rev | cut -d'.' -f 1 | rev

объяснение

  • rev меняет "maps.google.com" на moc.elgoog.spam
  • cut использует точку (т. е. «.») в качестве разделителя и выбирает первое поле, которое moc
  • наконец, мы снова обращаемся, чтобы получить com
zedfoxus
источник
6
Это не только использование, cutно это без sedили. Так awkчто думают OP?
Джаеш Бхой
7
@tom OP задала больше вопросов, чем просто за последние несколько часов. Основываясь на наших взаимодействиях с OP, мы знаем, что awk / sed / etc. не допускаются в его домашнем задании, но ссылка на rev не была сделана. Так что это стоило того
zedfoxus
4
@zfus я вижу. Может быть, захочется вставить другой revпотом.
Том
17
двойной revотличный идеал!
Ford Guo
6
Удивительно, просто, прекрасно, спасибо за объяснение тоже - не хватает людей, объясняющих каждый шаг в длинных цепочках переданных команд
Пит
128

Используйте расширение параметра. Это намного эффективнее, чем любая другая внешняя команда cut(или grep) включена.

data=foo,bar,baz,qux
last=${data##*,}

Смотрите BashFAQ # 100 для ознакомления с нативной обработкой строк в bash.

Чарльз Даффи
источник
3
@ErwinWessels: потому что bash очень медленный. Используйте bash для запуска конвейеров, а не для массовой обработки данных. Я имею в виду, это замечательно, если у вас уже есть одна строка текста в переменной оболочки, или если вы хотите while IFS= read -ra array_var; do :;done <(cmd)обработать несколько строк. Но для большого файла rev | cut | rev, вероятно, быстрее! (И, конечно, awk будет быстрее, чем это.)
Питер Кордес
2
@PeterCordes, awk будет быстрее для больших файлов, конечно, но для преодоления затрат на запуск с постоянным коэффициентом требуется немалый вклад. (Существуют также оболочки - например, ksh93 - с производительностью, близкой к awk, где синтаксис, приведенный в этом ответе, остается действительным; bash исключительно вялый, но даже близко не подходит к единственной доступной опции).
Чарльз Даффи
1
Спасибо @PeterCordes; как обычно, я думаю, у каждого инструмента есть свои варианты использования.
Эрвин Вессельс
1
На сегодняшний день это самый быстрый и краткий способ обрезки одной переменной внутри bashскрипта (при условии, что вы уже используете bashскрипт). Не нужно называть что-либо внешним.
Кен Шарп
1
@Balmipour ... однако, rev является специфическим для любой ОС вы используете , что обеспечивает его - это не стандартизировано во всех системах UNIX. Смотрите список глав для раздела POSIX по командам и утилитам - его там нет. И на самом деле не${var##prefix_pattern} относится к bash; он соответствует стандарту POSIX sh , см. конец раздела 2.6.2 (связанный), поэтому в отличие от него он всегда доступен в любой совместимой оболочке. rev
Чарльз Даффи
89

Это невозможно, используя просто cut. Вот способ использования grep:

grep -o '[^,]*$'

Замените запятую для других разделителей.

Том
источник
3
Чтобы сделать наоборот, и найти все, кроме последнего поля, сделайте:grep -o '^.*,'
Ариэль
2
Это было особенно полезно, потому что revдобавить проблему многобайтовых символов Unicode в моем случае.
Брайс
3
Я пытался сделать это на MinGW, но моя версия grep не поддерживает -o, поэтому я использовал, sed 's/^.*,//'который заменяет все символы до и включая последнюю запятую на пустую строку.
TamaMcGlinn
46

Без awk? ... Но это так просто с awk:

echo 'maps.google.com' | awk -F. '{print $NF}'

AWK - намного более мощный инструмент, чтобы иметь в своем кармане. -F если для разделителя полей NF - количество полей (также обозначает индекс последнего)

Амир Мехлер
источник
2
Это универсально и работает точно так, как и ожидалось каждый раз. В этом сценарии использование cutдля достижения конечного результата ОП аналогично использованию ложки для «нарезки» стейка (каламбур предназначен :)). awkэто нож для стейка.
Hickory420
3
Избегайте ненужного использования, echoкоторое может замедлить работу скрипта для длинных файлов awk -F. '{print $NF}' <<< 'maps.google.com'.
Anil_M
14

Есть несколько способов. Вы можете использовать это тоже.

echo "Your string here"| tr ' ' '\n' | tail -n1
> here

Очевидно, что ввод пробела для команды tr должен быть заменен нужным разделителем.

rjni
источник
Спасибо! то, что работает в busybox sh 1.0.0 :)
kevinf
1
Это похоже на самый простой ответ для меня, меньше труб и более ясный смысл
joeButler
1
Это не будет работать для всего файла, что, вероятно, и имел в виду OP.
Амир
7

Это единственное решение, которое можно использовать только для вырезания:

эхо "строка" | cut -d '.' -f2- [repeat_following_part_forever_or_until_out_of_memory:] | cut -d '.' -F2-

Используя это решение, количество полей действительно может быть неизвестно и время от времени изменяться. Однако, поскольку длина строки не должна превышать символов или полей LINE_MAX, включая символ новой строки, произвольное количество полей никогда не может быть частью реального условия этого решения.

Да, очень глупое решение, но единственное, которое соответствует критериям, я думаю.

Друг
источник
2
Ницца. Просто возьми последний "." от "строки", и это работает.
Мэтт
2
Я люблю, когда все говорят, что что-то невозможно, а потом кто-то вмешивается с рабочим ответом. Даже если это действительно очень глупо.
Бежор
Можно повторяться cut -f2-в цикле до тех пор, пока выход больше не изменится.
loa_in_
4

Если ваша входная строка не содержит прямой косой черты, вы можете использовать basenameи подоболочку:

$ basename "$(echo 'maps.google.com' | tr '.' '/')"

Это не использует sedили, awkно это также не использует cut, так что я не совсем уверен, если он квалифицируется как ответ на вопрос в качестве его формулировки.

Это не очень хорошо работает, если обрабатывать входные строки, которые могут содержать косую черту. Обходной путь для такой ситуации - заменить косую черту другим символом, который, как вы знаете, не является частью допустимой входной строки. Например, |символ pipe ( ) также не разрешен в именах файлов, поэтому это будет работать:

$ basename "$(echo 'maps.google.com/some/url/things' | tr '/' '|' | tr '.' '/')" | tr '|' '/'
jstine
источник
2

следующее реализует предложение друга

#!/bin/bash
rcut(){

  nu="$( echo $1 | cut -d"$DELIM" -f 2-  )"
  if [ "$nu" != "$1" ]
  then
    rcut "$nu"
  else
    echo "$nu"
  fi
}

$ export DELIM=.
$ rcut a.b.c.d
d
user2166700
источник
2
Вам нужны двойные кавычки вокруг аргументов, чтобы echoэто работало надежно и надежно. См stackoverflow.com/questions/10067266/...
tripleee
0

Если у вас есть файл с именем filelist.txt, который представляет собой список путей, например, следующий: c: /dir1/dir2/file1.h c: /dir1/dir2/dir3/file2.h

тогда вы можете сделать это: rev filelist.txt | cut -d "/" -f1 | оборот

aperson1961
источник
0

Добавляем подход к этому старому вопросу просто для удовольствия:

$ cat input.file # file containing input that needs to be processed
a;b;c;d;e
1;2;3;4;5
no delimiter here
124;adsf;15454
foo;bar;is;null;info

$ cat tmp.sh # showing off the script to do the job
#!/bin/bash
delim=';'
while read -r line; do  
    while [[ "$line" =~ "$delim" ]]; do
        line=$(cut -d"$delim" -f 2- <<<"$line")
    done
    echo "$line"
done < input.file

$ ./tmp.sh # output of above script/processed input file
e
5
no delimiter here
15454
info

Помимо bash, используется только разрез. Ну и эхо, наверное.

Каффе Майерс
источник
Мех, почему бы просто полностью не удалить cut и использовать только bash ... x], чтобы while read -r line; do echo ${line/*;}; done <input.fileполучить тот же результат.
Каффе Майерс
-1

Я понял, что если мы просто убедимся, что конечный разделитель существует, он работает. Так что в моем случае у меня есть запятые и пробелы. Я добавляю пробел в конце;

$ ans="a, b"
$ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
b
AnneTheAgile
источник
И ans="a, b, c"выдает b, что не соответствует требованиям «количество полей неизвестно или изменяется с каждой строкой» .
jww