J'ai un fichier d'entrée avec certaines sections qui sont délimitées avec des balises de début et de fin, par exemple:
line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
Je veux appliquer une transformation à ce fichier de telle sorte que les lignes X, Y, Z soient filtrées via une commande ( nl
, par exemple), mais le reste des lignes passe inchangé. Notez que nl
(lignes numériques) accumule l'état sur plusieurs lignes, donc ce n'est pas une transformation statique qui est appliquée à chacune des lignes X, Y, Z. ( Edit : il a été souligné que cela nl
peut fonctionner dans un mode qui ne nécessite pas d'état accumulé, mais je ne fais qu'utiliser nl
comme exemple pour simplifier la question. En réalité, la commande est un script personnalisé plus complexe. Ce que je cherche vraiment car est une solution générique au problème de l'application d'un filtre standard à une sous-section d'un fichier d'entrée )
La sortie doit ressembler à:
line A
line B
1 line X
2 line Y
3 line Z
line C
line D
Il peut y avoir plusieurs de ces sections dans le fichier qui nécessitent la transformation.
Mise à jour 2 Je n'ai pas spécifié à l'origine ce qui devait arriver s'il y avait plus d'une section, par exemple:
line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@inline-code-start
line L
line M
line N
@@inline-code-end
Je m'attendais à ce que l'état ne doive être maintenu que dans une section donnée, donnant:
line A
line B
1 line X
2 line Y
3 line Z
line C
line D
1 line L
2 line M
3 line N
mais, je pense qu'interpréter le problème comme exigeant que l'état soit conservé entre les sections est valide et utile dans de nombreux contextes.
Fin de la mise à jour 2
Ma première pensée est de construire une machine à états simple qui suit dans quelle section nous sommes:
#!/usr/bin/bash
while read line
do
if [[ $line == @@inline-code-start* ]]
then
active=true
elif [[ $line == @@inline-code-end* ]]
then
active=false
elif [[ $active = true ]]
then
# pipe
echo $line | nl
else
# output
echo $line
fi
done
Avec qui je cours:
cat test-inline-codify | ./inline-codify
Cela ne fonctionne pas car chaque appel à nl
est indépendant, donc les numéros de ligne n'augmentent pas:
line A
line B
1 line X
1 line Y
1 line Z
line C
line D
Ma prochaine tentative a été d'utiliser un fifo:
#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
if [[ $line == @@inline-code-start* ]]
then
active=true
elif [[ $line == @@inline-code-end* ]]
then
active=false
elif [[ $active = true ]]
then
# pipe
echo $line > myfifo
else
# output
echo $line
fi
done
rm myfifo
Cela donne la sortie correcte, mais dans le mauvais ordre:
line A
line B
line C
line D
1 line 1
2 line 2
3 line 3
Il y a probablement une mise en cache.
Est-ce que je me trompe? Cela semble être un problème assez générique. Je pense qu'il devrait y avoir un simple pipeline pour résoudre ce problème.
la source
nl
n'a pas à accumuler d'état . Regardeznl -d
et vérifiez vosman
/info
pages pour plus d'informations surnl
le délimiteur de section .nl
que comme exemple de filtre. Je pensais que cela simplifierait la question en masquant les détails de ce que faisait exactement le filtre, mais j'ai probablement causé plus de confusion. En fait, je filtre la sous-section via un surligneur de code, pour un générateur de blog statique maison. En ce moment, j'utilise gnusource-highlight
, mais cela pourrait changer, et je pourrais aussi ajouter plus de filtres, comme un formateur.Réponses:
Je suis d' accord avec vous - il probablement est un problème générique. Cependant, certains utilitaires communs ont certaines installations pour le gérer.
nl
nl
, par exemple, sépare l'entrée en pages logiques comme-d
délimitées par un délimiteur de section à deux caractères . Trois occurrences sur une ligne indiquent à elles seules le début d'un cap , deux le corps et un le pied de page . Il remplace tous ceux trouvés en entrée par une ligne vierge en sortie - qui sont les seules lignes vierges qu'il imprimeJ'ai modifié votre exemple pour inclure une autre section et je l'ai ajouté
./infile
. Il ressemble donc à ceci:Ensuite, j'ai exécuté ce qui suit:
nl
peut être chargé d' accumuler l'état sur les pages logiques, mais ce n'est pas le cas par défaut. Au lieu de cela, il numérotera les lignes de son entrée en fonction des styles et par section . Cela-ha
signifie donc numéroter toutes les lignes d'en- tête et-bn
signifie pas de lignes de corps - comme cela commence dans un corps état de .Jusqu'à ce que j'apprenne cela, je l'utilisais
nl
pour n'importe quelle entrée, mais après avoir réalisé que celanl
pouvait fausser la sortie selon son-d
élimiteur par défaut,\:
j'ai appris à être plus prudent avec lui et j'ai commencé à utilisergrep -nF ''
à la place pour une entrée non testée. Mais une autre leçon apprise ce jour-là est que celanl
peut être très utilement appliqué à d'autres égards - comme celui-ci - si vous modifiez juste un peu son entrée - comme je le faissed
ci-dessus.PRODUCTION
Voici un peu plus
nl
- remarquez-vous ci-dessus comment toutes les lignes sauf celles numérotées commencent par des espaces? Lorsque desnl
lignes numérotées, il insère un certain nombre de caractères dans la tête de chacun. Pour ces lignes, il ne numérote pas - même les blancs - il correspond toujours au retrait en insérant (-w
nombre d'-s
idées + séparateur len) * des espaces en tête des lignes non numérotées. Cela vous permet de reproduire le contenu non numéroté exactement en le comparant au contenu numéroté - et avec peu d'effort. Lorsque vous considérez quenl
cela divisera son entrée en sections logiques pour vous et que vous pouvez insérer des-s
chaînes arbitraires en tête de chaque ligne numérotée, il devient assez facile de gérer sa sortie:Les impressions ci-dessus ...
GNOU
sed
Si ce
nl
n'est pas votre application cible, un GNUsed
peute
exécuter une commande shell arbitraire pour vous en fonction d'une correspondance.Ci-dessus
sed
recueille les données d'entrée dans l'espace de motif jusqu'à ce qu'il en ait assez pour réussir la substitutionT
et arrêter leb
ranching vers l':l
abel. Quand il le fait, ile
xécutenl
avec une entrée représentée comme un<<
document ici pour tout le reste de son espace de motif.Le workflow est comme ceci:
/^@@.*start$/!b
^
ligne entière$
ne correspond!
pas au modèle ci-dessus, elle est retirée du script et imprimée automatiquement - donc à partir de ce moment, nous ne travaillons qu'avec une série de lignes commençant par le modèle./
/
b
s//nl <<\\@@/
s//
champ/
correspond à la dernière adressesed
essayée de correspondre - donc cette commande remplace la@@.*start
ligne entière à lanl <<\\@@
place.:l;N
:
commande définit une étiquette de branche - ici j'en ai défini une nommée:l
abel. LaN
commande ext ajoute la ligne d'entrée suivante à l'espace de motif suivie d'un\n
caractère de ligne électronique. C'est l'une des rares façons d'obtenir une ligne\n
électronique dans unsed
espace de motif - le\n
caractère de ligne électronique est un délimiteur sûr pour unsed
der qui le fait depuis un certain temps.s/\(\n@@\)[^\n]*end$/\1/
s///
substitution ne peut être réussie qu'après un début et uniquement lors de la première occurrence suivante d'une ligne de fin . Il\n
n'agira que sur un espace de motif dans lequel la dernière ligne électronique est immédiatement suivie en@@.*end
marquant la toute fin$
de l'espace de motif. Lorsqu'il agit, il remplace toute la chaîne correspondante par le\1
premier\(
groupe\)
, ou\n@@
.Tl
T
commande est se branche sur une étiquette (si elle est fournie) si une substitution réussie ne s'est pas produite depuis la dernière fois qu'une ligne d'entrée a été tirée dans l'espace modèle (comme je le fais avecN
) . Cela signifie que chaque fois qu'une ligne\n
électronique est ajoutée à un espace de modèle qui ne correspond pas à votre délimiteur de fin, laT
commande est échoue et se ramifie vers l':l
abel, ce qui entraîne l'sed
extraction de laN
ligne ext et la boucle jusqu'à ce qu'elle réussisse.e
Lorsque la substitution de la correspondance de fin est réussie et que le script ne se ramifie pas pour un
T
est échoué , exécutera une commande qui ressemble à ceci:sed
e
l
Vous pouvez le constater par vous-même en modifiant la dernière ligne pour ressembler à
Tl;l;e
.Il imprime:
while ... read
Une dernière façon de le faire, et peut-être la manière la plus simple, est d'utiliser une
while read
boucle, mais pour une bonne raison. Le shell - (plus particulièrement unbash
shell) - est généralement assez épouvantable pour gérer les entrées en grandes quantités ou en flux réguliers. Cela a également du sens - le travail du shell est de gérer l'entrée caractère par caractère et d'appeler d'autres commandes qui peuvent gérer les choses plus importantes.Mais ce qui est important à propos de son rôle, c'est que le shell ne doit pas
read
occuper une grande partie de l'entrée - il est spécifié de ne pas mettre en mémoire tampon d'entrée ou de sortie au point qu'il consomme tellement ou qu'il ne relaie pas suffisamment à temps que les commandes qu'il appelle sont laissées manquantes. - à l'octet. Fait doncread
un excellent test d' entrée - pourreturn
savoir s'il reste des entrées et vous devez appeler la prochaine commande pour la lire - mais ce n'est généralement pas la meilleure façon de procéder.Voici un exemple, cependant, de la façon dont on pourrait utiliser
read
et d' autres commandes pour traiter l'entrée en synchronisation:La première chose qui se produit pour chaque itération est de
read
tirer une ligne. Si elle réussit, cela signifie que la boucle n'a pas encore atteint EOF et que dans le cascase
elle correspond à un délimiteur de début, ledo
bloc est immédiatement exécuté. Sinon,printf
imprime le$line
onread
etsed
est appelé.sed
vap
rimer chaque ligne jusqu'à ce qu'il rencontre le début marqueur - quand ilq
uits entièrement entrée. Le-u
commutateur nbuffered est nécessaire pour GNUsed
car il peut tamponner plutôt avidement sinon, mais - selon les spécifications - les autres POSIXsed
devraient fonctionner sans aucune considération particulière - tant qu'il<infile
s'agit d'un fichier normal.Lors de la première
sed
q
sortie, le shell exécute ledo
bloc de la boucle - qui en appelle un autresed
qui imprime chaque ligne jusqu'à ce qu'il rencontre le marqueur de fin . Il dirige sa sortie verspaste
, car il imprime chacun des numéros de ligne sur leur propre ligne. Comme ça:paste
puis colle ceux ensemble sur:
caractères, et la sortie entière ressemble à ceci:Ce ne sont que des exemples - tout peut être fait dans le test ou dans les blocs ici, mais le premier utilitaire ne doit pas consommer trop d'entrée.
Tous les utilitaires impliqués lisent la même entrée - et impriment leurs résultats - chacun à leur tour. Ce genre de chose peut être difficile d'obtenir le blocage de - parce que différents utilitaires tampon plus que d' autres - mais vous pouvez généralement compter sur
dd
,head
etsed
de faire la bonne chose (bien que, pour GNUsed
, vous avez besoin du cli-switch) et vous devriez toujours pouvoir compterread
- car c'est, par nature, très lent . Et c'est pourquoi la boucle ci-dessus ne l'appelle qu'une seule fois par bloc d'entrée.la source
sed
exemple que vous avez donné, et cela fonctionne, mais j'ai vraiment du mal à fouiller la syntaxe. (mon sed est assez faible et se limite généralement à s / findthis / replacethis / g. Je vais devoir faire un effort pour m'asseoir et vraiment comprendre sed.)Une possibilité est de le faire avec l'éditeur de texte vim. Il peut diriger des sections arbitraires via des commandes shell.
Pour ce faire, vous pouvez utiliser les numéros de ligne en utilisant
:4,6!nl
. Cette commande ex s'exécutera nl sur les lignes 4-6 inclusivement, réalisant ce que vous voulez sur votre exemple d'entrée.Un autre moyen plus interactif consiste à sélectionner les lignes appropriées à l'aide du mode de sélection de ligne (shift-V) et des touches fléchées ou à rechercher, puis à l'aide de
:!nl
. Une séquence de commandes complète pour votre exemple d'entrée pourrait êtreCe n'est pas très adapté à l'automatisation (les réponses utilisant par exemple sed sont mieux pour cela), mais pour les modifications ponctuelles, il est très utile de ne pas avoir recours à des scripts shell de 20 lignes.
Si vous n'êtes pas familier avec vi (m), vous devez au moins savoir qu'après ces modifications, vous pouvez enregistrer le fichier en utilisant
:wq
.la source
HOME=$(pwd) vim -c 'call Mf()' f
. Si vous utilisez xargs, vous souhaiterez peut-être utiliser gvim sur un serveur x dédié pour éviter de corrompre votre tty (vnc est indépendant de la carte vidéo et peut être surveillé).La solution la plus simple à laquelle je peux penser est de ne pas utiliser
nl
mais de compter les lignes vous-même:Vous l'exécutez ensuite sur le fichier:
la source
Si votre objectif est d'envoyer le bloc de code entier à une seule instance de processus, vous pouvez accumuler les lignes et retarder la canalisation jusqu'à la fin du bloc de code:
Cela produit ce qui suit pour un fichier d'entrée qui répète le scénario de test trois fois:
Pour faire quelque chose d' autre avec le bloc de code, par exemple inverse, puis le numéro, juste l'envoie à autre chose:
echo -E "${acc:1}" | tac | nl
. Résultat:Ou nombre de mots
echo -E "${acc:1}" | wc
:la source
Modifier a ajouté une option pour définir un filtre fourni par l'utilisateur
Par défaut, le filtre est "nl". Pour modifier le filtre, utilisez l'option "-p" avec une commande fournie par l'utilisateur:
ou
Ce dernier filtre affichera:
Mise à jour 1 L'utilisation d'IPC :: Open2 a des problèmes de mise à l'échelle: si la taille de la mémoire tampon est dépassée, elle peut se bloquer. (dans ma machine, la taille du tampon du tube si 64K correspond à 10_000 x "ligne Y").
Si nous avons besoin de plus grandes choses (si nous avons besoin de plus de 10000 "ligne Y"):
(1) installer et utiliser
use Forks::Super 'open2';
(2) ou remplacer la fonction pipeit par:
la source
$/
et les
drapeau), et l'utilisation due
drapeau pour faire l'appel réel à la commande externe. J'aime vraiment le deuxième exemple (art ascii)!/s
= ("." signifie(.|\n)
);$/
redéfinit le séparateur de registre.C'est un travail pour awk.
Lorsque le script voit le marqueur de début, il note qu'il doit commencer à canaliser
nl
. Lorsque lapipe
variable est vraie (non nulle), la sortie est dirigée vers lanl
commande; lorsque la variable est fausse (non définie ou nulle), la sortie est imprimée directement. La commande canalisée est bifurquée la première fois que la construction de tube est rencontrée pour chaque chaîne de commande. Les évaluations ultérieures de l'opérateur de tuyau avec la même chaîne réutilisent le tuyau existant; une valeur de chaîne différente créerait un tuyau différent. Laclose
fonction ferme le canal pour la chaîne de commande donnée.Il s'agit essentiellement de la même logique que votre script shell utilisant un canal nommé, mais beaucoup plus facile à énoncer et la logique de fermeture est bien exécutée. Vous devez fermer le tuyau au bon moment pour faire
nl
sortir la commande en vidant ses tampons. Votre script ferme en fait le tuyau trop tôt: le tuyau est fermé dès que le premier aecho $line >myfifo
terminé son exécution. Cependant, lanl
commande ne voit la fin du fichier que si elle obtient une tranche de temps avant la prochaine exécution du scriptecho $line >myfifo
. Si vous aviez un grand volume de données, ou si vous ajoutezsleep 1
après avoir écrit dansmyfifo
, vous verrez quenl
ne traite que la première ligne ou le premier groupe rapide de lignes, puis il se ferme car il a vu la fin de son entrée.En utilisant votre structure, vous devez garder le tuyau ouvert jusqu'à ce que vous n'en ayez plus besoin. Vous devez avoir une redirection de sortie unique dans le tuyau.
(J'ai également profité de l'occasion pour ajouter des citations correctes et autres - voir Pourquoi mon script shell s'étouffe-t-il sur les espaces ou d'autres caractères spéciaux? )
Si vous faites cela, vous pouvez tout aussi bien utiliser un pipeline plutôt qu'un tube nommé.
la source
do
. (Je n'ai pas le représentant ici pour faire un petit montage.)D'accord, tout d'abord; Je comprends que vous ne cherchez pas à numéroter les lignes dans les sections de votre dossier. Puisque vous n'avez pas donné d'exemple réel de ce que pourrait être votre filtre (autre que
nl
), supposons qu'il soitc'est-à-dire, convertir le texte en majuscules; donc, pour une entrée de
vous voulez une sortie de
Voici ma première approximation d'une solution:
où les espaces avant les
@@
chaînes et près de la fin de la dernière ligne sont des tabulations. Veuillez noter que j'utilisenl
à mes propres fins . (Bien sûr, je le fais pour résoudre votre problème, mais pas pour vous donner une sortie numérotée.)Cela numérote les lignes de l'entrée afin que nous puissions la séparer au niveau des marqueurs de section et savoir comment la reconstituer plus tard. Le corps principal de la boucle est basé sur votre première tentative, en tenant compte du fait que les marqueurs de section portent des numéros de ligne. Il sépare l'entrée en deux fichiers:
file0
(inactif; pas dans une section) etfile1
(actif; dans une section). Voici à quoi ils ressemblent pour l'entrée ci-dessus:Ensuite, nous courons
file1
(qui est la concaténation de tous les lignes de section) le filtre de capitalisation; combinez cela avec les lignes hors-section non filtrées; trier, pour les remettre dans leur ordre d'origine; puis supprimez les numéros de ligne. Cela produit la sortie affichée en haut de ma réponse.Cela suppose que votre filtre laisse les numéros de ligne seuls. Si ce n'est pas le cas (par exemple, s'il insère ou supprime des caractères au début de la ligne), je pense que cette approche générale peut toujours être utilisée, mais nécessitera un codage légèrement plus délicat.
la source
nl
fait déjà la plupart du travail là-bas - c'est à cela que-d
sert son option d'élimitation.Un script shell qui utilise sed pour sortir des morceaux de lignes non délimitées et introduire des morceaux de lignes délimités dans un programme de filtrage:
J'ai écrit ce script dans un fichier nommé detagger.sh et l'ai utilisé comme suit:
./detagger.sh infile.txt
. J'ai créé un fichier filter.sh distinct pour imiter la fonctionnalité de filtrage dans la question:Mais l'opération de filtrage peut être modifiée dans le code.
J'ai essayé de suivre l'idée d'une solution générique avec ceci afin que les opérations comme les lignes de numérotation ne nécessitent pas de comptage supplémentaire / interne. Le script effectue une vérification rudimentaire pour voir que les balises de démarcation sont en paires et ne gère pas du tout les balises imbriquées avec élégance.
la source
Merci pour toutes ces bonnes idées. J'ai trouvé ma propre solution en gardant une trace de la sous-section dans un fichier temporaire et en la canalisant à la fois vers ma commande externe. C'est très similaire à ce que Supr a suggéré (mais avec une variable shell au lieu d'un fichier temporaire). De plus, j'aime vraiment l'idée d'utiliser sed, mais la syntaxe de ce cas semble un peu exagérée pour moi.
Ma solution:
(J'utilise
nl
juste comme exemple de filtre)Je préférerais ne pas avoir à gérer la gestion des fichiers temporaires, mais je comprends que les variables shell peuvent avoir des limites de taille assez faibles, et je ne connais aucune construction bash qui fonctionnerait comme un fichier temporaire, mais disparaîtrait automatiquement lorsque le le processus se termine.
la source
M
,N
etO
est numéroté4
,5
et6
. Cela ne fait pas ça. Ma réponse le fait (à part le fait que, dans son incarnation actuelle, il ne fonctionne pasnl
comme un filtre). Si cette réponse vous donne la sortie que vous voulez, alors qu'entendez-vous par «accumuler l'état sur plusieurs lignes»? Vouliez-vous dire que vous vouliez conserver l'état uniquement à travers chaque section, mais pas entre (entre) sections? (Pourquoi n'avez-vous pas mis un exemple en plusieurs sections dans votre question?)nl -p
pour obtenirM,N,O==4,5,6
.