Переставляйте стовпчики за допомогою вирізання


135

У мене є файл у такому форматі

Колонка1 Стовпчик2
str1 1
str2 2
str3 3

Я хочу, щоб стовпці були переставлені. Я спробував нижче команди

вирізати -f2,1 file.txt

Команда не змінює порядок стовпців. Будь-яка ідея, чому це не працює?

Дякую.

Відповіді:


148

Для cut(1)чоловічої сторінки:

Використовуйте один, і лише один із -b, -c або -f. Кожен СПИСОК складається з одного діапазону або багатьох діапазонів, розділених комами. Вибране введення записується в тому ж порядку, що і прочитане, і пишеться рівно один раз.

Спочатку воно доходить до поля 1, тобто друкується, а потім - поле 2.

Використовуйте awkзамість цього:

awk '{ print $2 " " $1}' file.txt

12
Це дуже погано cut, не підтримує цю інтуїтивну команду переупорядкування. У будь-якому разі, ще одна порада: ви можете використовувати awk's -FSта -OFSпараметри для використання спеціальних роздільників поля введення та виведення (як -dі --output-delimiterдля cut).
Малана

12
Вибачте, FSце варіант, OFSце змінна. наприкладawk -v OFS=";" -F"\t" '{print $2,$1}'
малана

2
Примітка для користувачів Windows Git Bash: якщо у вас є дивний вихід з команди вище, схожий на стовпці, що перекривають один одного, винна повернення каретки. Змініть EOL у вашому файлі з CRLF на LF.
jakub.g

1
Якщо ви не хочете змінювати вхідний файл, ви можете | sed 's/\r//' | awk
передати

2
Цей дуже простий, але може бути корисним для деяких, просто замініть пробіл на \ t для переупорядкування за вкладками, а якщо вам потрібно більше стовпців, ви можете зробити це, наприклад,awk '{print $4 "\t" $2 "\t" $6 "\t" $7}' file
FatihSarigol

64

Ви також можете комбінувати cutта paste:

paste <(cut -f2 file.txt) <(cut -f1 file.txt)

через коментарі: Можна уникнути башизмів і видалити один екземпляр вирізання, виконавши:

paste file.txt file.txt | cut -f2,3

3
Не впевнений, що це кваліфікується як "розумно", але: f = file.txt вставити <(cut -f2 $ f) <(cut -f1 $ f). Також зауважу, що цей метод найпростіший, коли у вас є багато стовпців і хочете переміщатися по великих блоках з них.
Майкл Раш

не працює з клітинками змінної довжини в одному стовпчику
крайник

2
@kraymer Що ти маєш на увазі? cutдобре працює для стовпців змінної довжини, якщо у вас є унікальний роздільник стовпців.
трійка

1
Щоб усунути зайвий файл, ви, ймовірно, можете використати tee:
JJW5432

2
Можна уникнути bashізмів та видалити один екземпляр cut, виконуючи такі дії: paste file.txt file.txt | cut -f2,3
agc

7

використовуючи лише оболонку,

while read -r col1 col2
do
  echo $col2 $col1
done <"file"

Це дуже часто неефективно. Як правило, ви виявите, що відповідний сценарій Awk відбувається набагато швидше, наприклад. Ви також повинні бути обережними, щоб процитувати значення "$col2"і "$col1"- у даних можуть бути метахарактеристики оболонок або інші шенагігани.
трійка

7

Ви можете використовувати Perl для цього:

perl -ane 'print "$F[1] $F[0]\n"' < file.txt
  • -e варіант означає виконання команди після неї
  • -n означає читання рядка за рядком (відкрийте файл, в цьому випадку STDOUT, і переведіть цикл на рядки)
  • -a означає поділ таких ліній на вектор, який називається @F ("F" - як поле). Perl індексує вектори, починаючи з 0, на відміну від cut, який індексує поля, починаючи з форми 1.
  • Ви можете додати шаблон -F (без пробілу між -F і шаблоном ), щоб використовувати шаблон як роздільник поля при читанні файлу замість пробілу за замовчуванням

Перевага запуску perl полягає в тому, що (якщо ви знаєте Perl) ви можете зробити набагато більше обчислень на F, ніж перестановка стовпців.


perlrun (1) стверджує -a неявно задає -n, але якщо я запускаю без набору n, воно, схоже, не циклічно. дивно.
Трентон

Яка версія? perl -ae printпрацює як catдля мене
pwes

5

Використання join:

join -t $'\t' -o 1.2,1.1 file.txt file.txt

Примітки:

  • -t $'\t'У GNU join більш інтуїтивним -t '\t' без$ збою ( Coreutils v8.28 і раніше?); це, мабуть, помилка, яка потрібна для вирішення проблеми $. Див.: Універсальний розділовий знак розділення .

  • joinпотрібні дві назви файлів, хоча над цим файлом працює лише один файл. Використання однойменної назви двічі хитрощів joinвиконувати бажану дію.

  • Для систем з низькими ресурсами joinпропонується менший слід, ніж деякі інструменти, що використовуються в інших відповідях:

    wc -c $(realpath `which cut join sed awk perl`) | head -n -1
      43224 /usr/bin/cut
      47320 /usr/bin/join
     109840 /bin/sed
     658072 /usr/bin/gawk
    2093624 /usr/bin/perl

3

Просто працюю над чимось дуже схожим, я не експерт, але я думав, що поділюсь командами, якими я користувався. У мене був мульти стовпець csv, від якого мені потрібно було лише 4 стовпчики, а потім мені потрібно було їх змінити.

У моєму файлі була труба '|' обмежено, але це можна замінити.

LC_ALL=C cut -d$'|' -f1,2,3,8,10 ./file/location.txt | sed -E "s/(.*)\|(.*)\|(.*)\|(.*)\|(.*)/\3\|\5\|\1\|\2\|\4/" > ./newcsv.csv

Дійсно, це дійсно грубо і готово, але його можна підлаштувати під костюм!


Це не дає відповіді на поставлене питання. В дусі переповнення стека, будь ласка, виділіть час, щоб відповісти на проблему перед тим, як опублікувати.
Білл Гейл

0

Використання sed

Використовуйте sed з вкладеними субдекспресіями основного регулярного вираження для збору та зміни порядку змісту стовпця. Цей підхід найкраще підходить, коли є обмежена кількість скорочень для впорядкування стовпців, як у цьому випадку.

Основна ідея полягає в оточенні цікавих частин шаблону пошуку з \(і \), які можна відтворити в шаблоні заміни, \#де #являє собою послідовне положення підвираження в шаблоні пошуку.

Наприклад:

$ echo "foo bar" | sed "s/\(foo\) \(bar\)/\2 \1/"

врожайність:

bar foo

Текст поза піддекспресією сканується, але не зберігається для відтворення в рядку заміни.

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

Розбіжні простори

Щоб проілюструвати найпростіше використання, припустимо, що декілька пробілів можна зібрати на одиничні пробіли, а значення другого стовпця закінчуються EOL (а не пробілом).

Файл:

bash-3.2$ cat f
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  nl
0000040    s   t   r   2  sp  sp  sp  sp  sp  sp  sp   2  nl   s   t   r
0000060    3  sp  sp  sp  sp  sp  sp  sp   3  nl 
0000072

Перетворити:

bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f
Column2 Column1
1 str1
2 str2
3 str3
bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  nl
0000020    1  sp   s   t   r   1  nl   2  sp   s   t   r   2  nl   3  sp
0000040    s   t   r   3  nl
0000045

Збереження ширини стовпців

Давайте тепер поширимо метод на файл із стовпцями постійної ширини, дозволяючи колонкам бути різної ширини.

Файл:

bash-3.2$ cat f2
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f2
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  sp
0000040   sp  sp  sp  sp  sp  nl   s   t   r   2  sp  sp  sp  sp  sp  sp
0000060   sp   2  sp  sp  sp  sp  sp  sp  nl   s   t   r   3  sp  sp  sp
0000100   sp  sp  sp  sp   3  sp  sp  sp  sp  sp  sp  nl
0000114

Перетворити:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2
Column2 Column1
1       str1      
2       str2      
3       str3      
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   2  sp  sp  sp  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

Нарешті, хоча приклад запитання не має рядків неоднакової довжини, цей вираз sed підтримує цей випадок.

Файл:

bash-3.2$ cat f3
Column1    Column2
str1       1      
string2    2      
str3       3      

Перетворити:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3
Column2 Column1   
1       str1      
2       string2   
3       str3    
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   i   n   g   2  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

Порівняння з іншими методами упорядкування стовпців під оболонкою

  • Дивно, але інструмент для обробки файлів, awk не дуже підходить для вирізання з поля до кінця запису. У sed це можна досягти, використовуючи регулярні вирази, наприклад, \(xxx.*$\)де xxxвираз відповідає стовпцю.

  • Використання вставлених і вирізаних підшах стає складним при впровадженні всередину скриптів оболонки. Код, який працює з командного рядка, не може проаналізувати, коли він введений всередині сценарію оболонки. Принаймні, це був мій досвід (який підштовхував мене до такого підходу).


0

Розширення на відповідь від @Met, також за допомогою Perl:
Якщо вхід і вихід обмежені TAB:

perl -F'\t' -lane 'print join "\t", @F[1, 0]' in_file

Якщо вхід і вихід обмежені пробілом:

perl -lane 'print join " ", @F[1, 0]' in_file

Тут,
-eкаже Perl шукати вбудований код, а не в окремому файлі сценарію,
-nчитає вхід 1 рядок за один раз,
-lвидаляє роздільник запису вхідних даних ( \nна * NIX) після зчитування рядка (аналогічно chomp) та додає вихід роздільник записів ( \nна * NIX) кожен print,
-aрозділяє вхідні лінію на пробільних в масив @F,
-F'\t'в комбінації з-a розколами вхідний лінії на ТКСЕ, замість пробілів в масив @F.

@F[1, 0]- це масив, що складається з 2-го та 1-го елементів масиву @Fв цьому порядку. Пам'ятайте, що масиви в Perl є індексованими нулем, тоді як поля в cut- 1-індексованими. Так поля в@F[0, 1] - ті ж поля, що і поля в cut -f1,2.

Зауважте, що такі позначення дозволяють гнучкіше маніпулювати введенням, ніж у деяких інших відповідях, розміщених вище (що чудово підходить для простого завдання). Наприклад:

# reverses the order of fields:
perl -F'\t' -lane 'print join "\t", reverse @F' in_file

# prints last and first fields only:
perl -F'\t' -lane 'print join "\t", @F[-1, 0]' in_file
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.