Як знайти останнє поле за допомогою 'cut'


310

Без використання sedабо awk, лише cut , як я можу отримати останнє поле, коли кількість полів невідома або змінюється з кожним рядком?


8
Ви закохані в cutкоманду :)? чому б не будь-які інші команди Linux?
Jayesh Bhoi

7
Без sedабо awk: perl -pe 's/^.+\s+([^\s]+)$/$1/'.
Йорданм


4
@MestreLion Багато разів люди читають запитання, щоб знайти рішення варіанта проблеми. Це починається з помилкової передумови, яка cutпідтримує те, чого він не робить. Але я вважав, що це корисно, оскільки воно змушує читача розглянути код, який простіше дотримуватися. Я хотів швидкий, простий спосіб використовувати cutбез необхідності використання кількох синтаксисів для awk, grep, sedі т.д. , revщо зробив трюк; дуже елегантно, і те, про що я ніколи не замислювався (навіть якщо незрозумілий для інших ситуацій). Мені також сподобалося читати інші підходи з інших відповідей.
Beejor

3
Тут виникла проблема з реальним життям: я хочу знайти всі різні розширення файлів у вихідному дереві та оновити файл .gitattributes. Так find | cut -d. -f<last>само і з природним нахилом
студог

Відповіді:


679

Ви можете спробувати щось подібне:

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

Пояснення

  • rev повертає "maps.google.com", щоб бути moc.elgoog.spam
  • cut використовує крапку (тобто '.') як роздільник, і вибирає перше поле, яке є moc
  • нарешті, ми повертаємо його знову, щоб отримати com

6
Це не тільки використання, cutале це без sedабо awk. Що ви думаєте, що ОП?
Jayesh Bhoi

7
@tom OP за останні кілька годин задало більше питань, ніж тільки це. На основі наших взаємодій з ОП ми знаємо, що awk / sed / тощо. не допускаються до його домашнього завдання, але посилання на rev не робилося. Тож варто було зняти
zedfoxus

4
@zfus Я бачу. Можливо, хочете приклеїти інший revзгодом.
Том

17
подвійний revчудовий ідеал!
Форд Гоо

6
Дивовижний, простий, ідеальний, дякую також за пояснення - недостатньо людей, що пояснюють кожен крок у довгих ланцюжках трубних команд
Піт,

128

Використовуйте розширення параметра. Це набагато ефективніше, ніж будь-яка зовнішня команда, що входить у комплект cut(або grep).

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

Дивіться BashFAQ # 100, щоб ознайомитись з нативною маніпуляцією з рядком в bash.


3
@ErwinWessels: Тому що баш справді повільний. Використовуйте bash для запуску конвеєрів, а не обробляти дані масово. Я маю на увазі, це чудово, якщо у вас є один рядок тексту, який вже є змінною оболонки, або якщо ви хочете зробити, while IFS= read -ra array_var; do :;done <(cmd)щоб обробити кілька рядків. Але для великого файлу rev | вирізати | rev, ймовірно, швидше! (І звичайно, awk буде швидшим за це.)
Пітер Кордес

2
@PeterCordes, awk буде швидшим для великого файлу, звичайно, але для вдосконалення витрат на запуск постійного фактора потрібен неабиякий вклад. (Існують також оболонки - на зразок ksh93 - з продуктивністю, ближчою до awk, де синтаксис, наведений у цій відповіді, залишається дійсним; bash є винятково млявим, але він навіть не є близьким до єдиного доступного варіанту).
Чарльз Даффі

1
Спасибі @PeterCordes; як завжди, я думаю, кожен інструмент має свої випадки використання.
Ервін Весселс

1
Це, безумовно, найшвидший і стисліший спосіб обрізання однієї змінної всередині bashсценарію (якщо ви вже використовуєте bashсценарій). Не потрібно називати нічого зовнішнього.
Кен Шарп

1
@Balmipour ... проте, rev є специфічним для будь-якої ОС ви використовуєте , що забезпечує його - це не стандартизовано в усіх системах UNIX. Дивіться перелік розділів для розділу POSIX про команди та утиліти - його там немає. І ${var##prefix_pattern}насправді не є специфічним для башти; це в стандарті 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 слід замінити на потрібний роздільник.


Дякую! те, що працює у busbox sh 1.0.0 :)
kevinf

1
Це відчувається як найпростіша відповідь для мене, менше дуд і чіткіший сенс
joeButler

1
Це не працюватиме для цілого файлу, а саме це, мабуть, означало ОП.
Амір

7

Це єдине можливе рішення, не використовуючи нічого, крім вирізаного:

відлуння "рядок" | вирізати -d '.' -f2- [повторити_наступний_част_форівер_ор_унтиль_ут_оф_мобіль:] | вирізати -d '.' -f2-

Використовуючи це рішення, кількість полів справді може бути невідомим і час від часу змінюватися. Однак, оскільки довжина рядка не повинна перевищувати LINE_MAX символів або полів, включаючи символ нового рядка, то довільна кількість полів ніколи не може бути частиною справжньої умови цього рішення.

Так, дуже дурне рішення, але єдине, що відповідає критеріям, я думаю.


2
Приємно. Просто візьміть останнє "." off "string", і це працює.
Метт

2
Я люблю, коли всі кажуть, що щось неможливо, і тоді хтось звучить робочою відповіддю. Навіть якщо це справді дуже нерозумно.
Beejor

Можна було повторювати 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 '|' '/'


0

Якщо у вас є файл з ім'ям filelist.txt, який є списком шляхів, таких як: c: /dir1/dir2/file1.h c: /dir1/dir2/dir3/file2.h

тоді ви можете зробити це: rev filelist.txt | вирізати -d "/" -f1 | об


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 ... 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

І ans="a, b, c"створює b, що не відповідає вимогам "кількість полів невідомі або змінюються з кожним рядком" .
jww
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.