Короткий і портативний "приєднання" в командному рядку Unix


77

Як я можу об’єднати кілька рядків в один рядок із роздільником, де були символи нового рядка, уникаючи кінцевого роздільника та, за бажанням, ігноруючи порожні рядки?

Приклад. Розглянемо текстовий файл foo.txtіз трьома рядками:

foo
bar
baz

Бажаний результат:

foo,bar,baz

Команда, яку я використовую зараз:

tr '\n' ',' <foo.txt |sed 's/,$//g'

В ідеалі це було б приблизно так:

cat foo.txt |join ,

Що:

  1. найбільш портативний, стислий, читабельний спосіб.
  2. найбільш стислий спосіб із використанням нестандартних інструментів unix.

Звичайно, я міг щось написати, або просто використати псевдонім. Але мені цікаво знати варіанти.


Відповіді:


130

Можливо, трохи дивно, pasteце хороший спосіб зробити це:

paste -s -d","

Це не стосуватиметься порожніх рядків, про які ви згадали. Для цього grepспочатку прокладіть текст :

grep -v '^$' | paste -s -d"," -

@codaddict Ні я, але я повинен визнати, що я взагалі не вважаю це інтуїтивним - мені завжди потрібно перевіряти сторінки з інформацією про це. Мені точно цікаво подивитися, що пропонують інші.
Michael J. Barber

Є й інші способи, але жоден із них не приємніший (а найцікавіші трохи похмурі).
сорпігаль

Здається, це не ігнорує порожні рядки, але це все одно дуже приємно і працює для мого випадку використання. Дякую!
прикладом

13
Для покращеної переносимості розгляньте можливість додавання -в кінці pasteкоманди кожного разу, коли передбачається читання stdin. (Деякі версії paste, такі як BSD, не читатимуться, stdinякщо їм -явно не передано.)
kjo 02.03.13

2
Дякуємо за підказку про paste! Я помітив, що він допускає лише символи з роздільниками, і це \tза замовчуванням. Для досягнення довших роздільників (наприклад , ):cat foo.txt | paste -s | sed 's/\t/, /g'
Арілд

12

Цей sedоднорядковий рядок повинен працювати -

sed -e :a -e 'N;s/\n/,/;ba' file

Тест:

[jaypal:~/Temp] cat file
foo
bar
baz

[jaypal:~/Temp] sed -e :a -e 'N;s/\n/,/;ba' file
foo,bar,baz

Щоб обробляти порожні рядки, ви можете видалити порожні рядки та направити їх до вищевказаного однокласника.

sed -e '/^$/d' file | sed -e :a -e 'N;s/\n/,/;ba'

Пояснення було б непоганим!
Tejas Kale

1
Це більш ясно , щоб об'єднати два -e вираження в один, sed -e ':a; N; s/\n/,/; ba'. Але це все ще метод O (n²), оскільки sed буде виконувати заміну кожного разу, коли буде додано новий рядок. sed -e ':a; N; $!ba; s/\n/,/g'є лінійним, підставляючи лише один раз після того, як усі рядки додаються до простору візерунків sed. $!baозначає "якщо це останній рядок ($), не (!) переходити до мітки (b): a (a), розірвати цикл"
zhazha

8

Як щодо використання ксарг?

для вашої справи

$ cat foo.txt | sed 's/$/, /' | xargs

Будьте обережні щодо граничної довжини введення команди xargs. (Це означає, що дуже довгий вхідний файл не може бути оброблений цим.)


Я знайшов -L прапор на xargs корисним -L 5050 пунктів на рядок.
jmunsch

6

Perl:

cat data.txt | perl -pe 'if(!eof){chomp;$_.=","}'

або ще коротший і швидший, на диво:

cat data.txt | perl -pe 'if(!eof){s/\n/,/}'

або, якщо ви хочете:

cat data.txt | perl -pe 's/\n/,/ unless eof'

2
Найприємніше в цьому - ви можете використовувати будь-який рядок замість простої коми. Прийнята відповідь менш універсальна. Мені особливо подобається остаточна ітерація, хоча я б написав її так: perl -pe 's/\n/,/ unless eof' data.txt (не потрібен фальшивий кіт).
Mike S

4

Для розваги, ось всебічне рішення

IFS=$'\n' read -r -d '' -a data < foo.txt ; ( IFS=, ; echo "${data[*]}" ; )

Ви можете використовувати printfзамістьecho якщо кінцевий рядок є проблемою.

Це працює шляхом встановлення IFSроздільників, які readбудуть розділятися, лише на новий рядок, а не на інші пробіли, а потім повідомляти, readщо не слід припиняти читання, поки не досягне значення a nul, замість нового рядка, який він зазвичай використовує, і додати кожен прочитаний елемент у масив ( -a) даних. Потім, в субоболочке так, щоб не затирати IFSінтерактивну оболонку, ми встановлюємо , IFSщоб ,і розширити масив *, який обмежує кожен елемент масиву з першим символомIFS


1
цікаво, проте портативність не є чудовою, оскільки -dв команді чистої shоболонки немає можливості read.
mykhal

@mykhal: Правда. Однак bashйого можна знайти у багатьох системах, тому він має деяку корисність. Якщо ви хочете, щоб масиви переносимості теж, мабуть, теж вийшли, інакше ви можете просто використовувати whileцикл, щоб обійти відсутність -d. Для належної портативної вбудованої версії ви хотіли б щось подібне, c= ; while IFS= read -r d ; do if ! [ -z "$d" ] ; then printf "$c$d" ; fi c=, ; done < foo.txtале вона все ще не працює, тому readщо знає -r, але це може бути опущено, і передбачає вбудованийprintf , тому echo, мабуть, там краще, якщо ефективність важлива. Все-таки прийнята відповідь набагато краща!
sorpigal

0

Мені потрібно було зробити що - щось подібне, друк , розділених комами список полів з файлу, і був щасливий з пилу STDOUT до xargsі ruby, наприклад , так:

cat data.txt | cut -f 16 -d ' ' | grep -o "\d\+" | xargs ruby -e "puts ARGV.join(', ')"

0

У мене був файл журналу, де деякі дані були розбиті на кілька рядків. Коли це сталося, останнім символом першого рядка була крапка з комою (;). Я приєднався до цих рядків, використовуючи такі команди:

for LINE in 'cat $FILE | tr -s " " "|"'
do
    if [ $(echo $LINE | egrep ";$") ]
    then
        echo "$LINE\c" | tr -s "|" " " >> $MYFILE
    else
        echo "$LINE" | tr -s "|" " " >> $MYFILE
    fi
done

Результат - файл, де рядки, розділені в журналі, були одним рядком у моєму новому файлі.


0

Простий спосіб об’єднати рядки за допомогою простору на місці ex(також ігноруючи порожні рядки), використовуйте:

ex +%j -cwq foo.txt

Якщо ви хочете надрукувати результати у стандартному виданні, спробуйте:

ex +%j +%p -scq! foo.txt

Щоб об'єднати рядки без пробілів, використовуйте +%j!замість+%j .

Якщо використовувати різні роздільники, це трохи складніше:

ex +"g/^$/d" +"%s/\n/_/e" +%p -scq! foo.txt

де g/^$/d(або v/\S/d) видаляє порожні рядки і s/\n/_/є заміною, яка в основному працює так само, як і використання sed, але для всіх рядків ( %). Після завершення розбору надрукуйте буфер ( %p). І нарешті, -cq!виконуючи q!команду vi , яка в основному завершує роботу без збереження (-s полягає у приглушенні виводу).

Зверніть увагу, що exеквівалентноvi -e .

Цей метод досить портативний, оскільки більшість Linux / Unix за замовчуванням постачаються з ex/ vi. І він є більш сумісним, ніж використання, sedде параметр in-place ( -i) не є стандартним розширенням, а сам утиліта більш орієнтований на потік, тому він не такий портативний.


-1

Моя відповідь:

awk '{printf "%s", ","$0}' foo.txt

printfдостатньо. Нам не потрібно -F"\n"міняти роздільник поля.


1
Це додає помилкову кому на початку виводу. -1 за тестування.
Mike S
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.