Comment extraire une plage prédéterminée de lignes d'un fichier texte sous Unix?

532

J'ai un vidage SQL de ~ 23000 lignes contenant plusieurs bases de données pour des données. J'ai besoin d'extraire une certaine section de ce fichier (c'est-à-dire les données d'une seule base de données) et de la placer dans un nouveau fichier. Je connais les numéros de ligne de début et de fin des données que je veux.

Quelqu'un connaît-il une commande Unix (ou une série de commandes) pour extraire toutes les lignes d'un fichier entre disons les lignes 16224 et 16482, puis les rediriger dans un nouveau fichier?

Adam J. Forster
la source
Puisque vous mentionnez des fichiers volumineux, je suggère de vérifier le commentaire stackoverflow.com/questions/83329/…
sancho.s ReinstateMonicaCellio

Réponses:

793
sed -n '16224,16482p;16483q' filename > newfile

Du manuel sed :

p - Imprimez l'espace du motif (sur la sortie standard). Cette commande n'est généralement utilisée qu'en conjonction avec l'option de ligne de commande -n.

n - Si l'impression automatique n'est pas désactivée, imprimez l'espace du motif, puis, malgré tout, remplacez l'espace du motif par la ligne d'entrée suivante. S'il n'y a plus d'entrée, sed quitte sans traiter d'autres commandes.

q - Quittez sedsans traiter d'autres commandes ou entrées. Notez que l'espace de motif actuel est imprimé si l'impression automatique n'est pas désactivée avec l'option -n.

et

Les adresses d'un script sed peuvent prendre l'une des formes suivantes:

nombre La spécification d'un numéro de ligne correspondra uniquement à cette ligne dans l'entrée.

Une plage d'adresses peut être spécifiée en spécifiant deux adresses séparées par une virgule (,). Une plage d'adresses correspond à des lignes à partir de l'endroit où la première adresse correspond et continue jusqu'à ce que la deuxième adresse corresponde (inclusivement).

boxxar
la source
3
J'étais curieux de savoir si cela modifiait le fichier d'origine. Je l'ai sauvegardé juste au cas où et il semble que cela n'ait PAS modifié l'original, comme prévu.
Andy Groff
@AndyGroff. Pour modifier le fichier en place, utilisez le paramètre "-i". Sinon, il ne modifiera pas le fichier.
youri
175
Si, comme moi, vous devez le faire sur un fichier TRÈS volumineux, cela aide si vous ajoutez une commande quit sur la ligne suivante. Alors c'est sed -n '16224,16482p;16483q' filename. Sinon sed continuera à scanner jusqu'à la fin (ou du moins ma version le fait).
wds
7
Les gens @MilesRout semblent demander "pourquoi le downvote?" assez souvent, peut-être que vous voulez dire "je m'en fiche" au lieu de "personne ne s'en soucie"
Mark
1
@wds - Votre commentaire mérite bien une réponse qui grimpe au sommet. Cela peut faire la différence entre le jour et la nuit.
sancho.s ReinstateMonicaCellio
203
sed -n '16224,16482 p' orig-data-file > new-file

Où 16224,16482 sont le numéro de ligne de départ et le numéro de ligne de fin, inclus. Ceci est indexé 1. -nsupprime l'écho de l'entrée comme sortie, ce que vous ne voulez clairement pas; les nombres indiquent la plage de lignes sur laquelle exécuter la commande suivante; la commande pimprime les lignes pertinentes.

JXG
la source
7
Sur les fichiers volumineux, la commande ci-dessus continuera à parcourir l'intégralité du fichier une fois la plage souhaitée trouvée. Existe-t-il un moyen pour que sed arrête de traiter le fichier une fois la plage sortie?
Gary
39
Eh bien, de la réponse ici , il semble que l' arrêt à la fin de la gamme pourrait se faire avec: sed -n '16224,16482p;16482q' orig-data-file > new-file.
Gary
5
Pourquoi voudriez-vous mettre dans un espace inutile, puis devoir citer? (Bien sûr, créer des problèmes inutiles et les résoudre est l'essence de la moitié de l'informatique, mais je veux dire à côté de cette raison ...)
Kaz
92

Assez simple avec tête / queue:

head -16482 in.sql | tail -258 > out.sql

en utilisant sed:

sed -n '16482,16482p' in.sql > out.sql

en utilisant awk:

awk 'NR>=10&&NR<=20' in.sql > out.sql
manveru
la source
1
Les deuxième et troisième options sont OK, mais la première est plus lente que de nombreuses alternatives car elle utilise 2 commandes où 1 est suffisante. Cela nécessite également le calcul pour obtenir le bon argument tail.
Jonathan Leffler
3
Il convient de noter que pour conserver les mêmes numéros de ligne que la question, la commande sed devrait être sed -n 16224,16482p' in.sql >out.sqlet la commande awk devrait êtreawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz
3
A savoir également que dans le cas du premier exemple head -16482 in.sql | tail -$((16482-16224)) >out.sqllaisse le calcul à bash
sibaz
1
Le premier avec tête et queue WAYYYY plus rapide sur les gros fichiers que la version sed, même avec l'option q ajoutée. head-version version instantanée et sed I Ctrl-C après une minute ... Merci
Miyagi
2
Pourrait également être utilisé tail -n +16224pour réduire le calcul
SOFe
35

Vous pouvez utiliser 'vi' puis la commande suivante:

:16224,16482w!/tmp/some-file

Alternativement:

cat file | head -n 16482 | tail -n 258

EDIT: - Juste pour ajouter une explication, vous utilisez head -n 16482 pour afficher les premières 16482 lignes puis utilisez tail -n 258 pour extraire les 258 dernières lignes de la première sortie.

Mark Janssen
la source
2
Et au lieu de vi, vous pouvez utiliser ex, c'est-à-dire vi moins la console interactive.
Tadeusz A. Kadłubowski
1
Vous n'avez pas besoin de la catcommande; headpeut lire un fichier directement. C'est plus lent que de nombreuses alternatives car il utilise 2 (3 comme indiqué) commandes où 1 est suffisant.
Jonathan Leffler
1
@JonathanLeffler Vous avez tout à fait tort. C'est incroyablement rapide. J'extrais 200k lignes, environ 1G, à partir d'un fichier 2G avec 500k lignes, en quelques secondes (sans le cat). D'autres solutions nécessitent au moins quelques minutes. La variation la plus rapide sur GNU semble également être tail -n +XXX filename | head XXX.
Antonis Christofides
28

Il existe une autre approche avec awk:

awk 'NR==16224, NR==16482' file

Si le fichier est volumineux, il peut être utile de exitlire la dernière ligne souhaitée. De cette façon, il ne lira pas inutilement les lignes suivantes:

awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file

awk 'NR==16224, NR==16482; NR==16482 {exit}' file
fedorqui 'SO arrête de nuire'
la source
2
1+ pour économiser le temps d'exécution et les ressources en utilisant print; exit. Merci !
Bernie Reiter
Légère simplification du 2ème exemple:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Robin A. Meade
C'est brillant, merci @ RobinA.Meade! J'ai édité votre idée dans le post
fedorqui 'SO stop harming'
17
perl -ne 'print if 16224..16482' file.txt > new_file.txt
mmaibaum
la source
9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2
Cetra
la source
6
cat dump.txt | head -16224 | tail -258

devrait faire l'affaire. L'inconvénient de cette approche est que vous devez effectuer l'arithmétique pour déterminer l'argument de queue et pour savoir si vous voulez que «entre» inclue la ligne de fin ou non.

JP Lodine
la source
4
Vous n'avez pas besoin de la catcommande; headpeut lire un fichier directement. C'est plus lent que de nombreuses alternatives car il utilise 2 (3 comme indiqué) commandes où 1 est suffisant.
Jonathan Leffler
@JonathanLeffler Cette réponse est la plus facile à lire et à retenir. Si vous vous souciez vraiment des performances, vous n'auriez pas utilisé un shell en premier lieu. Il est recommandé de laisser des outils spécifiques se consacrer à une certaine tâche. De plus, "l'arithmétique" peut être résolue en utilisant | tail -$((16482 - 16224)).
Yeti
6

Debout sur les épaules de boxxar, j'aime ça:

sed -n '<first line>,$p;<last line>q' input

par exemple

sed -n '16224,$p;16482q' input

Cela $signifie "dernière ligne", donc la première commande fait sedimprimer toutes les lignes commençant par la ligne 16224et la deuxième commande fait sedquitter après l' impression de la ligne 16428. (Ajout 1pour leq plage -r dans la solution de boxxar ne semble pas nécessaire.)

J'aime cette variante car je n'ai pas besoin de spécifier deux fois le numéro de ligne de fin. Et j'ai mesuré que l'utilisation $n'a pas d'effets néfastes sur les performances.

Tilman Vogel
la source
5

sed -n '16224,16482p' < dump.sql

cubex
la source
3

Rapide et sale:

head -16428 < file.in | tail -259 > file.out

Ce n'est probablement pas la meilleure façon de le faire, mais cela devrait fonctionner.

BTW: 259 = 16482-16224 + 1.

jan.vdbergh
la source
C'est plus lent que de nombreuses alternatives car il utilise 2 commandes où 1 est suffisant.
Jonathan Leffler
3

J'ai écrit un programme Haskell appelé splitter qui fait exactement cela: lire mon article de blog .

Vous pouvez utiliser le programme comme suit:

$ cat somefile | splitter 16224-16482

Et c'est tout ce qu'il y a à faire. Vous aurez besoin de Haskell pour l'installer. Juste:

$ cabal install splitter

Et vous avez terminé. J'espère que vous trouverez ce programme utile.

Robert Massaioli
la source
Ne splitterlit qu'à partir de l'entrée standard? Dans un sens, cela n'a pas d'importance; la catcommande est superflue qu'elle le fasse ou non. Soit utiliser splitter 16224-16482 < somefileou (s'il prend des arguments de nom de fichier) splitter 16224-16482 somefile.
Jonathan Leffler
3

Même nous pouvons le faire pour vérifier en ligne de commande:

cat filename|sed 'n1,n2!d' > abc.txt

Par exemple:

cat foo.pl|sed '100,200!d' > abc.txt
Chinmoy Padhi
la source
6
Vous n'avez pas besoin de la catcommande dans aucun de ceux-ci; sedest parfaitement capable de lire des fichiers seul, ou vous pouvez rediriger l'entrée standard d'un fichier.
Jonathan Leffler
3

Utilisation de rubis:

ruby -ne 'puts "#{$.}: #{$_}" if $. >= 32613500 && $. <= 32614500' < GND.rdf > GND.extract.rdf
Carl Blakeley
la source
2

J'étais sur le point de publier le truc tête / queue, mais en fait, je déclencherais probablement des emacs. ;-)

  1. esc- xgoto-line ret16224
  2. marque ( ctrl- space)
  3. esc- xgoto-line ret16482
  4. esc-w

ouvrir le nouveau fichier de sortie, ctl-y enregistrer

Voyons ce qui se passe.

sammyo
la source
4
D'après mon expérience, Emacs ne fonctionne pas très bien sur des fichiers très volumineux.
Greg Mattes
Pouvez-vous l'exécuter comme une action scriptée, ou s'agit-il uniquement d'une option interactive?
Jonathan Leffler
2

J'utiliserais:

awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt

FNR contient le numéro d'enregistrement (ligne) de la ligne lue dans le fichier.

Paddy3118
la source
2

Je voulais faire la même chose à partir d'un script en utilisant une variable et je l'ai réalisé en mettant des guillemets autour de la variable $ pour séparer le nom de la variable du p:

sed -n "$first","$count"p imagelist.txt >"$imageblock"

Je voulais diviser une liste en dossiers séparés et trouvé la question initiale et répondre à une étape utile. (La commande split n'est pas une option sur l'ancien système d'exploitation sur lequel je dois porter le code).

KevinY
la source
1

J'ai écrit un petit script bash que vous pouvez exécuter à partir de votre ligne de commande, tant que vous mettez à jour votre PATH pour inclure son répertoire (ou vous pouvez le placer dans un répertoire qui est déjà contenu dans le PATH).

Utilisation: $ pinch filename start-line end-line

#!/bin/bash
# Display line number ranges of a file to the terminal.
# Usage: $ pinch filename start-line end-line
# By Evan J. Coon

FILENAME=$1
START=$2
END=$3

ERROR="[PINCH ERROR]"

# Check that the number of arguments is 3
if [ $# -lt 3 ]; then
    echo "$ERROR Need three arguments: Filename Start-line End-line"
    exit 1
fi

# Check that the file exists.
if [ ! -f "$FILENAME" ]; then
    echo -e "$ERROR File does not exist. \n\t$FILENAME"
    exit 1
fi

# Check that start-line is not greater than end-line
if [ "$START" -gt "$END" ]; then
    echo -e "$ERROR Start line is greater than End line."
    exit 1
fi

# Check that start-line is positive.
if [ "$START" -lt 0 ]; then
    echo -e "$ERROR Start line is less than 0."
    exit 1
fi

# Check that end-line is positive.
if [ "$END" -lt 0 ]; then
    echo -e "$ERROR End line is less than 0."
    exit 1
fi

NUMOFLINES=$(wc -l < "$FILENAME")

# Check that end-line is not greater than the number of lines in the file.
if [ "$END" -gt "$NUMOFLINES" ]; then
    echo -e "$ERROR End line is greater than number of lines in file."
    exit 1
fi

# The distance from the end of the file to end-line
ENDDIFF=$(( NUMOFLINES - END ))

# For larger files, this will run more quickly. If the distance from the
# end of the file to the end-line is less than the distance from the
# start of the file to the start-line, then start pinching from the
# bottom as opposed to the top.
if [ "$START" -lt "$ENDDIFF" ]; then
    < "$FILENAME" head -n $END | tail -n +$START
else
    < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
fi

# Success
exit 0
DrNerdfighter
la source
1
C'est plus lent que de nombreuses alternatives car il utilise 2 commandes où 1 est suffisant. En fait, il lit le fichier deux fois à cause de la wccommande, ce qui gaspille la bande passante du disque, en particulier sur les fichiers gigaoctets. À bien des égards, cela est bien documenté, mais c'est aussi une surpuissance technique.
Jonathan Leffler
1

Cela pourrait fonctionner pour vous (GNU sed):

sed -ne '16224,16482w newfile' -e '16482q' file

ou profiter de bash:

sed -n $'16224,16482w newfile\n16482q' file
potong
la source
1

Utilisation de ed:

ed -s infile <<<'16224,16482p'

-ssupprime la sortie de diagnostic; les commandes réelles sont dans une chaîne ici. Plus précisément, 16224,16482pexécute la pcommande (impression) sur la plage d'adresses de ligne souhaitée.

Benjamin W.
la source
0

Le -n dans les réponses acceptées fonctionne. Voici une autre façon au cas où vous seriez enclin.

cat $filename | sed "${linenum}p;d";

Cela fait ce qui suit:

  1. canalisez le contenu d'un fichier (ou introduisez le texte comme vous le souhaitez).
  2. sed sélectionne la ligne donnée, l'imprime
  3. d est requis pour supprimer les lignes, sinon sed supposera que toutes les lignes seront éventuellement imprimées. c'est-à-dire, sans le d, vous obtiendrez toutes les lignes imprimées par la ligne sélectionnée imprimées deux fois parce que vous avez la partie $ {linen} p demandant qu'elle soit imprimée. Je suis sûr que le -n fait essentiellement la même chose que le d ici.
ThinkBonobo
la source
3
la note cat file | sedest mieux écrite commesed file
fedorqui 'SO arrêtez de nuire'
De plus, cela ne fait qu'imprimer une ligne, alors que la question concerne une gamme d'entre eux.
fedorqui 'SO arrêtez de nuire'
0

Puisque nous parlons d'extraire des lignes de texte d'un fichier texte, je donnerai un cas spécial où vous voulez extraire toutes les lignes qui correspondent à un certain modèle.

myfile content:
=====================
line1 not needed
line2 also discarded
[Data]
first data line
second data line
=====================
sed -n '/Data/,$p' myfile

Imprime la ligne [Données] et le reste. Si vous voulez que le texte de la ligne 1 au motif, vous tapez: sed -n '1, / Data / p' monfichier. De plus, si vous connaissez deux modèles (mieux vaut être unique dans votre texte), les lignes de début et de fin de la plage peuvent être spécifiées avec des correspondances.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Kemin Zhou
la source