Bash: объединить данные из двух файлов csv

Тот факт, что вы можете что-то делать в bash , не означает, что вы должны . Сценарии

sh bash и т. Д.) Лучше всего подходят в качестве относительно простых оболочек для запуска программ или команд обработки текста. Для более сложных задач, включая анализ файлов ini и работу с ними, больше подходят другие языки. Думали ли вы о написании сценария на perl или python ? У обоих есть хорошие парсеры файлов .ini - я несколько раз использовал модуль Perl Config :: INI , когда мне нужно было разобрать файл ini.

Но если вы настаиваете на использовании bash, вам следует использовать ассоциативный массив вместо установки отдельных переменных.

Начните примерно так:

#! /bin/bash

inifile='user1074170.ini' 

# declare $config to be an associative array
declare -A config

while IFS='=' read -r key val ; do 
    config["$key"]="$val"
done <  <(sed -E -e '/^\[/d
                     s/#.*//
                     s/[[:blank:]]+$|^[[:blank:]]+//g' "$inifile" )

# now print out the config array
set | grep '^config='

Скрипт sed удаляет строку [Section1] (фактически, все строки, начинающиеся с открытой квадратной скобки [ - вы захотите обработать это по-другому [1] в ini-файле с несколькими разделами) и удалить комментарии, а также начальные и конечные пробелы. Цикл while считывает каждую строку, используя = в качестве разделителя полей, и присваивает содержимое переменным $ key и $ val, которые затем добавляются в массив $ config.

Вывод:

config=([value1]="abc\`def" [value3]="mno\$pqr" [value2]="ghi>jkl" [value4]="stu;vwx" )

Вы можете использовать записи массива позже в вашем скрипте следующим образом:

$ echo value1 is "${config[value1]}"
value1 is abc`def

$ [ "${config[value4]}" = 'stu;vwx' ] && echo true
true

[1] awk или perl имеют удобные простые способы чтения файлов в режиме «абзаца». Абзац определяется как блок текста, отделенный от других блоков текста одной или несколькими пустыми строками.

например.чтобы работать только с [Section1] , вставьте ниже скрипт awk непосредственно перед скриптом sed , вводящим в цикл while выше:

awk -v RS= -v ORS='\n\n' '/\[Section1\]/' "$inifile" | sed ...

(и, конечно, удалите "$ inifile" из конца командной строки sed - вы не хотите загружать файл снова после того, как вы перешли в проблема извлечения из него только [Section1] ).

Настройка ORS не является строго необходимой, если вы извлекаете только один раздел из ini-файла, но будет полезна для сохранения разделения абзацев, если вы извлекаете два или более разделов.

3
03.10.2018, 00:09
4 ответа

Используйте специализированный инструмент для выполнения подобных задач (, также известный как база данных):

# Remove spaces around the field separator
sed -i.fixed 's/ *\; */\;/g' a
sed -i.fixed 's/ *\; */\;/g' b

# Add to sqlite database
echo -e '.separator ";"\n.import a.fixed a' | sqlite3 db.sqlite
echo -e '.separator ";"\n.import b.fixed b' | sqlite3 db.sqlite

# Select whatever you need
echo -e 'select a.username,a.mail,b."AccountStatus (locked=0 or unlocked=1)" from a join b on a.username = b.username;' | sqlite3 db.sqlite

awkраствор:

users=( $(awk -F";" 'NR>1{print $1";"$3}' a) )
for u in "${users[@]}"; do
    username=$(echo "$u" | cut -d';' -f1)
    mail=$(echo "$u" | cut -d';' -f2)
    awk -v "u=$username" -v "m=$mail" -F';' 'NR>1 { if ($3 == 0) print "User "u" ("m") is locked"; }' b
done
-1
27.01.2020, 21:25

Во-первых, если у вас есть пробелы вокруг разделителя, вам нужно удалить их в своем скрипте, как сказал @RoVo. Команды sed сделают это за вас.

Во-вторых, вы в основном хотите, чтобы цикл while считывал каждую строку из фиксированного файла A и получал имя пользователя и адрес электронной почты, а также, возможно, полное имя пользователя. Затем вы хотите проверить статус этого пользователя в фиксированном файле B.

Что-то вроде следующего небольшого цикла должно помочь вам начать работу:

#!/bin/bash

# Remove spaces around delimiter
sed -i.fixed 's/[       ]*\;[   ]*/\;/g' fileA
sed -i.fixed 's/[       ]*\;[   ]*/\;/g' fileB

# Read in each line from the fixed fileA
while read l; do

  # Skip the header line
  [[ ${l} =~ ^username ]] && continue

  # Get the user from the line that was read in.
  u=$(echo ${l} | awk -F\; '{print $1}')

  # Get the lock status for that user from the fixed fileB
  l=$(awk -F\; -v u=${u} '{if ($1 == u) {print $3}}' fileB.fixed)

  # Echo out the 2 fields.
  echo ${u}=${l}

  # Other stuff can go here.
done <fileA.fixed

exit 0
-1
27.01.2020, 21:25

Используя awk, сначала прочитайте имена пользователей, чья учетная запись заблокирована, из второго файла, затем извлеките их адреса электронной почты из первого файла (, затем надейтесь, что им не нужно входить в систему, чтобы прочитать их электронные письма):

awk -F ';' 'NR == FNR && $NF == 0    { names[$1] }
            NR != FNR && $1 in names { print $NF }' B.csv A.csv

Это предполагает, что каждое имя пользователя имеет одинаковое количество пробелов вокруг себя в обоих файлах. Если это не так, вы можете использовать -F ' *; *', чтобы включить любые символы пробела в разделитель, который использует awk. Также предполагается, что в данных нет встроенных символов ;.

NR— номер записи (строки )текущей записи в целом, а FNR— тот же номер, но в текущем файле. Если NR == FNR, то читаем из первого файла, заданного в командной строке (B.csv). NF— количество полей (столбцов )в текущей записи, $NF— данные в последнем поле (и $1— данные в первом поле ).

В приведенном выше коде используется ассоциативный массив/хэш, namesс ключом для имен заблокированных -пользователей, считанных из первого файла(B.csv). $1 in namesбудет истинным, если $1является ключом в этом массиве.

Зацикливание:

awk -F ';' 'NR == FNR && $NF == 0    { names[$1] }
            NR != FNR && $1 in names { print $NF }' B.csv A.csv |
while read addr; do
    printf 'Would send an email to "%s"\n' "$addr"
    #mail -s 'Account locked' "$addr" <template-email.txt
done

Или что-то в этом роде.Чтение адресов электронной почты таким образом в цикле удалит все пробелы вокруг них. Приведенный выше цикл не отправляет электронные письма, а печатает адреса, на которые необходимо отправить. Удалите #передmail(и напишите электронную форму в template-email.txt), чтобы на самом деле отправить электронное письмо (, но вы можете сделать это по-другому ).


Использованиеcsvkit:

csvjoin -d ';' -c 1 A.csv B.csv |
csvgrep -c 5 -m False |
csvcut -S -c 3 | sed 1d

CSVkit предоставляет инструменты анализа CSV для работы с CSV-файлами. Это необходимо, если ваши данные CSV не являются «простыми», т. е. если они используют правила CSV для цитирования встроенных символов ;и т. д. Приведенный выше конвейер будет

  1. Соедините два файла по именам пользователей (пробелы имеют значение ).
  2. Извлеките данные для заблокированных пользователей (0будет изменено на Falseна этом этапе конвейера ).
  3. Извлеките адреса электронной почты.
  4. Удалите заголовок CSV (с помощью последней команды sed).
5
27.01.2020, 21:25
#!/bin/bash 

cat fileA.txt | sed 1d | while IFS=';' read -r line; do #read fileA.txt starting with line #2
name=$(echo $line | awk '{print $1}') #find names in each line/column 1 of the table 
lock_status=$(grep $name fileB.txt | awk '{print $5}') # find lock/unlock status in fileB.txt

    if [[ "$lock_status" -eq 0 ]];then 

    echo "Locked: To mail the user : replace echo by the command mail";

        else

    echo "unlocked";
     fi
done
-1
27.01.2020, 21:25

Теги

Похожие вопросы