Изменить порядок столбцов с помощью вырезания


135

У меня есть файл в следующем формате

Столбец1 Столбец2
ул1 1
ул2 2
ул3 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и -OFSoptions для использования настраиваемых разделителей полей ввода и вывода (например, -dи --output-delimiterдля cut).
malana

12
Извините, FSэто вариант, OFSэто переменная. egawk -v OFS=";" -F"\t" '{print $2,$1}'
malana

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

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)

через комментарии: можно избежать башизмов и удалить один экземпляр cut, выполнив:

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

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

не работает с ячейками переменной длины в одном столбце
Краймер

2
@kraymer Что ты имеешь в виду? cutотлично работает для столбцов переменной длины, если у вас есть уникальный разделитель столбцов.
tripleee

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"- в данных могут быть метасимволы оболочки или другие махинации.
tripleee

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 и раньше?); вероятно, это ошибка, требующая обходного пути $. См .: unix join separator char .

  • 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

Transform:

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

Transform:

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      

Transform:

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:
Если ввод и вывод разделены табуляцией:

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

Если ввод и вывод разделены пробелами:

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

Здесь
-ePerl сообщает Perl искать код в строке, а не в отдельном файле сценария,
-nсчитывает ввод по одной строке за раз,
-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.