Intereting Posts
рекомендации по монтажу и разминированию акций Установите затвор на Centos ddrescue: Как повторить попытку для 1 плохого блока после того, как все другие данные надежно сохранены? Как указать пакеты RPM, установленные в порядке установки Получите первое соответствие шаблону в строке, не использующей разрез Почему через некоторое время я теряю связь с моим хостом по IPv6 при использовании ip6tables? Как печатать первые три строки и последние две строки файла, используя perl? Как я могу заставить sudo забыть пароль автоматически после одной команды? find -exec не работает в рыбе CSV> Excel> График? файл cat для многоадресной рассылки Поиск пользователя с высоким использованием ресурсов системы, использующей Sar rsync претендует на разные файлы, но diff нет? iptables и проблема политики маршрутизации источника Запуск моего собственного приложения при запуске

Как я могу применить `cut` к нескольким файлам, а затем« вставить »результаты?

Я часто делаю операции вроде

paste <(cut -d, -f1 file1.csv) <(cut -d, -f1 file2.csv) 

который очень утомительный с более чем несколькими файлами.

Могу ли я автоматизировать этот процесс, например, с помощью globbing? Я могу сохранить результаты cut с помощью

 typeset -A cut_results for f in file*.csv; do cut_results[$f]="$(cut -d, -f1 $f)" done 

но я не уверен, как исходить оттуда.

Вы можете автоматизировать это с помощью globbing, в частности, для определения e glob , плюс eval , но это некрасиво, и цитата сложна:

 eval paste *.csv(e\''REPLY="<(cut -d, -f1 $REPLY)"'\') 
  • Часть между \'…\' – это некоторый код для выполнения для каждого соответствия шара. Он выполняется с переменной REPLY установленной в соответствие, и может ее модифицировать.
  • Я помещаю код в одинарные кавычки, чтобы он не расширялся при анализе glob.
  • Код REPLY="<(cut -d, -f1 $REPLY)" генерирует строку <(cut -d, -f1 file1.csv) если совпадение – file1.csv . Двойные кавычки необходимы, чтобы часть после знака равенства не расширялась при выполнении кода e кроме подстановки значения REPLY .
  • Поскольку каждый файл globbed заменяется на строку,

Было бы лучше скрыть сложность функции. Минимально проверены.

 function map { emulate -LR zsh local cmd pre cmd=() while [[ $# -ne 0 && $1 != "--" ]]; do cmd+=($1) shift done if ((!$#)); then echo >&2 "Usage: $0: COMMAND [ARGS...] -- PREPROCESSOR [ARGS...] -- FILES..." return 125 fi shift while [[ $# -ne 0 && $1 != "--" ]]; do pre+="${(q)1} " shift done if ((!$#)); then echo >&2 "Usage: $0: COMMAND [ARGS...] -- PREPROCESSOR [ARGS...] -- FILES..." return 125 fi shift eval "${(@q)cmd}" "<($pre${(@q)^@})" } 

Пример использования (синтаксис напоминает zargs ):

 map paste -- cut -d, -f1 -- *.csv 

Попробовать awk

 awk '{L[FNR]=L[FNR] $1 "\t"}END{for(i=1;i<=FNR;i++)print L[i]}' *.csv 

или вставить с помощью sed

 paste *.csv | sed 's/ [^\t]*//g' 

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

Если есть множество файлов со всеми разными именами, вы можете уменьшить повторяющуюся типизацию с помощью простого расширения истории «чит»:

Первый запуск <(cut -d, -f1

Обратите внимание на конечное пространство. Также обратите внимание, что эта команда даст вам дополнительную подсказку; просто нажмите Ctrl- C . Единственный момент – добавить его в историю.

Далее запустите paste !!file1.csv) !!file2.csv)

!! будет расширяться до полного содержимого предыдущего запуска команды, включая конечное пространство. Обратите внимание: если вы забудете закрывающиеся круглые скобки, вы получите вторичное приглашение; вы можете просто набрать Ctrl- C и повторить попытку, если это произойдет.

Это немного взломано, но достаточно хорошо для одноразового использования. Если вы делаете это много, вы можете написать функцию bash.

Я bash скрипты bash в настоящий момент, и это казалось отличной простой задачей для практики, поэтому я написал следующее. (Мой другой ответ дает простой взлом для расширения истории, но это полный скрипт, и я счел его достойным получения дополнительного ответа.) Я считаю, что это совместимо с POSIX и должно работать с #!/bin/sh , но не на 100% конечно. EDIT: На самом деле, =~ не совместим с POSIX. Вы могли бы взять этот чек и позволить cut вернуть ошибку.

 #!/bin/bash fieldtocut=1 delimiter=',' usage () { cat << EOF usage: $0 [-f FIELD] [-d DELIMITER] file1.. Cuts field FIELD from each file and pastes it. Default field is 1, default delimiter is ',' EOF exit $1 } while getopts ':f:d:' opt ; do case $opt in f) if [[ $OPTARG =~ ^[0-9]+$ ]] ; then fieldtocut="$OPTARG" else usage 1 fi ;; d) delimiter=$OPTARG ;; *) usage 1 ;; esac done shift $((OPTIND-1)) [ $# -eq 0 ] && usage 0 pasteargs='' for file in "$@" ; do pasteargs=$(printf '%s' "$pasteargs" '<(cut -d$delimiter -f$fieldtocut ' "$file" ') ') done eval paste $pasteargs 

Предполагая, что ваши аргументы находятся в "$@" , я верю что-то вроде:

 eval "paste $(printf "<( cut -d, -f1 %q ) " "$@")" 

должен это сделать.

Вот еще один способ сделать это, что очень похоже на ответ от Wildcard :

 files=( file1.csv file2.csv) eval paste "<( cut -d, -f1 ${^files[@]} )" 

Вместо цикла for используется расширение ${^ ... } которое является Zsh-специфичным.

Сначала должны быть назначены files причин, так что globbing всегда выполняется последним, поэтому если files необходимо сгенерировать автоматически (как в files=( *.csv ) ), то что-то вроде ${^:-( *.csv )} будет расширяться только после того, как все другие расширения произошли. Мы хотим, чтобы он расширился первым .

Расширение ${^ ... } заставляет результирующий массив действовать как результат расширения скобки. Например, присвойте x=(ab) а затем сравните echo ${x}y с echo ${^x}y .

Цитирование необходимо обмануть Zsh для обработки окружающего текста, как буквальная строка. В противном случае он разделил бы командную строку в пространствах, поэтому наше расширение ${^ ... } уменьшилось бы до ""${^ ... }"" ; то есть каждый элемент будет окружен пустой строкой. То есть,

 echo "<( cut -d, -f1 ${^files[@]} )" 

а также

 echo "<( cut -d, -f1 "\ ${^files[@]}\ " )" 

эквивалентны, но не совпадают с

 echo <( cut -d, -f1 ${^files[@]} ) 

Но цитирование вводит новую проблему: командная строка анализируется и разделяется без учета расширения. То есть, хотя мы эффективно вошли

 paste <( cut -d, -f1 file1.csv ) <( cut -d, -f1 file2.csv ) 

по желанию, это фактически анализируется как

 paste '<( cut -d, -f1 file1.csv )' '<( cut -d, -f1 file2.csv )' 

Поэтому нам нужно eval для повторного анализа правильно сформированного выражения. Чтобы увидеть это в действии, сравните

 setopt noxtrace eval paste "<( cut -d, -f1 ${^files[@]} )" 1>/dev/null 2>&1 

в

 setopt xtrace eval paste "<( cut -d, -f1 ${^files[@]} )" 1>/dev/null 2>&1 

Я надеялся, что некоторая комбинация вложенных расширений, расширение ${ ... :- ... } и флаги расширения параметров Q , z и / или s приведут к переоценке без eval , но, очевидно, это не дело. Я также хочу, чтобы был способ заставить глотать, но опять же это кажется невозможным.

Вы можете получить awk чтобы перебирать файлы в lockstep и сообщать интересующее поле из каждого файла. Поместите этот код в файл, скажем cut_files.awk

 NR == FNR{printf "%s%s",$1, FS; for (k=2; k<ARGC; ++k) {getline < ARGV[k]; printf "%s%s", $1, k==ARGC-1?"\n":FS}; next}; NR != FNR{for (k=2; k<ARGC; ++k) close(ARGV[k]); exit} 

И тогда назовите его так

 awk -F',' -f cut_files.awk file1 file2 file3 file4 ....