Ряд команд sed работает в командной строке, но не в скрипте

9

Я работаю с .csvвыводом этого запроса данных SE, который выглядит следующим образом (только с 5022 записями):

"{
  ""id"": 281952,
  ""title"": ""Flash 11.2 No Longer Supported by Google Play""
}"
"{
  ""id"": 281993,
  ""title"": ""Netbeans won't open in Ubuntu""
}"

(И у него есть ^Mокончания строки между [число] и "" заголовок ""). Мне нужно, чтобы это выглядело так:

281952,Flash 11.2 No Longer Supported by Google Play
281993,Netbeans won't open in Ubuntu

Я исправил это в определенном текстовом редакторе, который довольно легко останется безымянным, но я хотел создать скрипт, чтобы мне не приходилось делать его снова каждый раз при обновлении запроса, чтобы другие могли его использовать. Я использовал sed...

Эта серия команд работает отлично (хотя вполне может быть неэффективной; это всего лишь метод проб и ошибок):

# Print the ^M and remove them, write to a new file:
cat -v QueryR* | sed 's/\^M//' > QueryNew
# remove all the other junk:
sed -i 's/{//' QueryNew
sed -i 's/}//' QueryNew
sed -i 's/""//g' QueryNew
sed -i 's/^"//' QueryNew
sed -i '/,/{N;/\n.*title:\s/{s/,\n.*title:\s/,\ /}}' QueryNew
sed -i 's/^\s\+//' QueryNew
sed -i '/^\s*$/d' QueryNew
sed -i 's/^id:\ //' QueryNew
sed -i 's/,\ /,/' QueryNew
sed -i 's/\\//g' QueryNew

Так почему бы и нет? Только ^Mи {}удаляются, а все остальное еще есть.

#!/bin/bash
cat -v QueryR* | sed 's/\^M//' > QueryNew
sed -i '{
       s/{//
       s/}//
       s/""//g
       s/^"//
       /,/{N;/\n.*title:\s/{s/,\n.*title:\s/,\ /}}
       s/^\s\+//
       /^\s*$/d
       s/^id:\ //
       s/,\ /,/
       s/\\//g
}' QueryNew

Я уверен, что моя ошибка действительно очевидна ...

Занна
источник

Ответы:

11

Использование cat -vпревратить символы CR в буквенные ^Mпоследовательности кажется принципиально некрасиво мне - если вам нужно удалить окончания DOS строки, использование dos2unix, trили sed 's/\r$//"

Если вы настаиваете на использовании СЭД, то я предлагаю вам напечатать биты вы действительно хотите, а не пытаться удалить все случайные биты вы нет - например ,

$ sed -rn -e 's/\"//g' -e 's/(.*): (.*)\r/\2/p' QueryR | paste -d '' - -
281952,Flash 11.2 No Longer Supported by Google Play
281993,Netbeans won't open in Ubuntu

Вы можете получить фантазию и свернуть удаление кавычек в извлечение значения ключа, сопоставляя ноль или более кавычек на каждом конце последовательности значений

$ sed -rn 's/(.*): \"*([^"]*)\"*\r/\2/p' QueryR | paste -d '' - -
281952,Flash 11.2 No Longer Supported by Google Play
281993,Netbeans won't open in Ubuntu

Вы можете получить действительно фантазии и подражать pasteв sedсначала присоединения пар линий на ,\r$окончание , а затем соответствующие пары ключ-значение многократно ( g) и не жадностью

$ sed -rn '/,\r$/ {N; s/([^:]*): \"*([^:"]*)\"*\r\n?/\2/gp}' QueryR
281952,Flash 11.2 No Longer Supported by Google Play
281993,Netbeans won't open in Ubuntu

(Лично я предпочел бы подход KISS и использовал бы первый).


FWIW, так как ваши входные данные выглядят как JSON с завышенными кавычками, я бы предложил установить правильный анализатор JSON, такой как jq

sudo apt-get install jq

Вы можете сделать что-то вроде

$ sed -e 's/["]["]/"/g' -e 's/"{/{/' -e 's/}"/}/' QueryR | jq '.id, .title' | paste -d, - -
281952,"Flash 11.2 No Longer Supported by Google Play"
281993,"Netbeans won't open in Ubuntu"

который удаляет лишние кавычки, а затем использует их jqдля извлечения интересующих полей - обратите внимание, что, jqкажется, он обрабатывает окончания строк в стиле DOS, поэтому нет необходимости предпринимать специальные шаги для их удаления.

Перейдите на jq '.[]'сброс всех пар атрибут-значение.

Благодарность за вдохновение и базовый jqсинтаксис взяты из Преодоления новых строк с помощью grep -o

steeldriver
источник
1
тьфу да, idk, почему я забыл \r. jqразбит на первую строку, где поле заголовка имеет двоеточие (первая строка). Я до сих пор не знаю , почему sedменя ненавидит, но я убил некоторых из цитат и \rв этой линии , /,\r*/{N;/\n.*title.*:\s/{s/,\r*\n.*title.*:\s/,\ /}}и , наконец , она работает , как это . Большое спасибо ^ _ ^
Занна
1
Это НАМНОГО лучше (но я не хочу, чтобы какие-либо цитаты были такими же, sed -rn -e 's/\"\"//g' -e 's/^(.*): (.*)\r$/\2/p' QueryR* | paste -d '' - - как по волшебству)
Zanna
5

Я исправил это благодаря Steeldriver и дальнейшим изменениям. Не определено, но работает.

sed  '{
       s/"{//
       s/}"//
       s/^"//
       /,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,/}}
       s/""//g
       s/^\s\+//
       /^\s*$/d
       s/^id:\ //
       s/\\//g
}' QueryR* | tee "$1"

перевод:
s/"{//Удалить "{
s/}"//Удалить }"
s/^"//Удалить "из начала
/,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,\ /}}совпадения строк ,\rв одной строке и [whatever]title[whatever]:в следующей строке заменить все это на ,
s/""//gУдалить все оставшиеся двойные двойные кавычки
s/^\s\+//Удалить пробелы в начале строк
/^\s*$/dУдалить пустые строки
s/^id:\ //Удалить id:и пробел после него
s/\\//gУдалить обратные слэши (экранирующие символы для "добавлено в некоторые поля заголовка)
tee "$1"укажите выходной файл при запуске скрипта, например./queryclean newquery.csv

Занна
источник
4

Пока вопрос задается sed, можно обойти проблемы sed с Python:

from __future__ import print_function
import sys

with open(sys.argv[1]) as f:
     for line in f:
         if '""id""' in line:
            print(line.strip().split(':')[1],end="")
         if '""title""' in line:
            title = " ".join(line.strip().split(':')[1:])
            print(title.replace('""'," "))

Этот код совместим как с python2, так и с python3, поэтому любой из них будет работать

Образец прогона:

bash-4.3$ cat questions.txt 
"{
  ""id"": 281952,
  ""title"": ""Flash 11.2 No Longer Supported by Google Play""
}"
"{
  ""id"": 281993,
  ""title"": ""Netbeans won't open in Ubuntu""
}"
bash-4.3$ python3 parse_questions.py questions.txt 
 281952,  Flash 11.2 No Longer Supported by Google Play 
 281993,  Netbeans won't open in Ubuntu 
Сергей Колодяжный
источник
4

Еще три подхода:

  1. AWK

    $ awk -F'": ' '/\"id\"/{id=$NF;} 
                  /\"title\"/{
                    t=$NF; 
                    sub(/^""/,"",t); 
                    sub(/""$/,"",t); 
                    print id,t
                  }' OFS="" file 
    281952,Flash 11.2 No Longer Supported by Google Play
    281993,Netbeans won't open in Ubuntu
  2. Perl

    $ perl -lne '$id=$1 if /id"":\s*(\d+)/; 
                 if(/title"":\s*""(.*)""/){print "$id,$1"}' file 
    281952,Flash 11.2 No Longer Supported by Google Play
    281993,Netbeans won't open in Ubuntu
  3. GNU grep с регулярными выражениями, совместимыми с Perl, и простым Perl:

    $ grep -oP '(id"":\s*\K.*)|(title"":\s*""\K.*(?=""))' file | 
        perl -pe 'chomp if $.%2'
    281952,Flash 11.2 No Longer Supported by Google Play
    281993,Netbeans won't open in Ubuntu
terdon
источник
4

Это не совсем ответ на ваш вопрос или решение вашей проблемы, но чтобы избавиться от нежелательных символов, вы можете использовать tr :

cat QueryR | tr -d '}{:"' 

и вы получите:

Введите описание изображения здесь

kcdtv
источник
спасибо, мне нужно научиться использовать tr:)
Zanna
Это не так мощно, как sed или awk, но это очень просто для такого рода вещей. Ура :)
kcdtv
1

Это еще один скрипт, написанный на Ruby. Он сохранит запятые в заголовке, которые можно легко импортировать в любую программу электронных таблиц, не разбивая столбцы.

csvfile = File.open('query-fixed.csv', 'w')

File.open('QueryResults2.csv') do |f|
    content = f.read
    content.gsub!(/\r\n?/, "\n")
    content.each_line do |line|
        id, title = '', ''
        if line.match('\"id\"')
            id = line.split(':')[1].strip[0..-2]
            csvfile.write(id + ',')
        end
        if line.match('\"title\"')
            title = line.partition(':')[2].scan(/"(.*)"/)[0][0]
            csvfile.write(title + "\n")
        end
    end
end

После запуска программы полученный вывод будет выглядеть так:

281952,"Flash 11.2 No Longer Supported by Google Play"
281993,"Netbeans won't open in Ubuntu"
Анвар
источник
Это очень мило :)
Занна
Как насчет заголовков с :внутри них?
Снаđошƒаӽ
@ Sнаđошƒаӽ ой! Спасибо за указатель. Исправлено сейчас!
Анвар