Préserver les espaces blancs en lisant > > écrire un fichier ligne par ligne dans bash
-
22-07-2019 - |
Question
J'essaie de parcourir un répertoire de fichiers texte et de les combiner en un seul document. Cela fonctionne très bien, mais les fichiers texte contiennent des extraits de code et toute ma mise en forme est réduite à gauche. Tous les espaces sur une ligne sont supprimés.
#!/bin/sh
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
echo "Processing $f file..."
echo "">$OUTPUT
cat $f | while read line; do
echo "$line">>$OUTPUT
done
echo >>$OUTPUT
echo >>$OUTPUT
done
Je suis certes un bash noob, mais après avoir cherché haut et bas, je n’ai pas pu trouver une solution adéquate. Apparemment, BASH déteste l’espace blanc dominant en général.
La solution
Au lieu de:
cat $f | while read line; do
echo "$line">>$OUTPUT
done
Faites ceci:
cat $f >>$OUTPUT
(S'il y a une raison pour laquelle vous devez faire les choses ligne par ligne, il serait bon de l'inclure dans la question.)
Autres conseils
Comme d'autres l'ont déjà fait remarquer, utiliser cat ou awk à la place d'une boucle lecture-écho est un moyen bien plus efficace de le faire - évite le problème de la réduction des espaces (et de quelques autres que vous n'avez pas rencontrés par hasard), fonctionne plus vite, et au moins avec chat, est simplement un code plus propre. Néanmoins, je voudrais essayer de faire en sorte que la boucle lecture-écho fonctionne correctement.
Premièrement, le problème de la réduction des espaces: la commande de lecture supprime automatiquement les espaces de début et de fin; cela peut être corrigé en modifiant sa définition des espaces en définissant la variable IFS sur vide. En outre, read suppose qu'une barre oblique inversée à la fin de la ligne signifie que la ligne suivante est une continuation et qu'elle doit être jointe à celle-ci. pour résoudre ce problème, utilisez son drapeau -r (raw). Le troisième problème réside dans le fait que de nombreuses implémentations d’écho interprètent les séquences d’échappement dans la chaîne (par exemple, elles peuvent transformer \ n en une nouvelle ligne); pour résoudre ce problème, utilisez printf à la place. Enfin, à titre de règle générale d'hygiène de script, vous ne devez pas utiliser cat lorsque vous n'en avez pas réellement besoin. utilisez plutôt la redirection d'entrée. Avec ces modifications, la boucle interne ressemble à ceci:
while IFS='' read -r line; do
printf "%s\n" "$line">>$OUTPUT
done <$f
... il y a aussi quelques autres problèmes avec le script environnant: la ligne qui essaie de définir FILES comme la liste des fichiers .textile entourés par des guillemets, ce qui signifie qu'il n'est jamais développé dans une liste réelle de fichiers . La meilleure façon de faire est d’utiliser un tableau:
FILES=(../best-practices/*.textile)
...
for f in "${FILES[@]}"
(et toutes les occurrences de $ f doivent être entre guillemets au cas où l'un des noms de fichiers comporte des espaces ou d'autres caractères amusants - devrait le faire également avec $ OUTPUT, bien que, comme cela est défini dans le script, réellement en sécurité de partir.)
Enfin, il y a un echo "" > $ OUTPUT
près du sommet des fichiers en boucle qui vont effacer le fichier de sortie à chaque fois (c.-à-d. à la fin, il ne contient que le dernier fichier .textile); cela doit être déplacé avant la boucle. Je ne sais pas si l'intention ici était de mettre une seule ligne vierge au début du fichier ou trois lignes vides entre les fichiers (une au début et deux à la fin), alors je ne sais pas exactement quoi le remplacement approprié est. Quoi qu’il en soit, voici ce que je peux faire après avoir réglé tous ces problèmes:
#!/bin/sh
OUTPUT="../best_practices.textile"
FILES=(../best-practices/*.textile)
: >"$OUTPUT"
for f in "${FILES[@]}"
do
echo "Processing $f file..."
echo >>"$OUTPUT"
while IFS='' read -r line; do
printf "%s\n" "$line">>"$OUTPUT"
done <"$f"
echo >>"$OUTPUT"
echo >>"$OUTPUT"
done
C’est un moyen trop coûteux de combiner des fichiers.
cat ../best-practices/*.textile > ../best_practices.textile
si vous souhaitez ajouter un blanc (nouvelle ligne) à chaque fichier lors de la concaténation, utilisez awk
awk 'FNR==1{print "">"out.txt"}{print > "out.txt" }' *.textile
OU
awk 'FNR==1{print ""}{print}' file* > out.txt
Cela vous permet de séparer les nouvelles lignes entre chaque fichier d'entrée comme vous l'avez fait dans votre script d'origine:
for f in $FILES; do echo -ne '\n\n' | cat "$f" -; done > $OUTPUT
Notez que $ FILES
n'est pas indiqué pour que cela fonctionne (sinon les nouvelles lignes supplémentaires n'apparaissent qu'une seule fois à la fin de la sortie), mais $ f
doit être cité pour protéger les espaces dans les noms de fichiers, s'ils existent.
La bonne réponse, imo, est this , reproduit ci-dessous:
while IFS= read line; do
check=${line:0:1}
done < file.txt
Notez que cela résoudra les situations dans lesquelles l'entrée est acheminée depuis une autre commande, et pas seulement depuis un fichier réel.
Notez que vous pouvez également simplifier la redirection comme indiqué ci-dessous.
#!/bin/bash
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
echo "Processing $f file..."
{
echo
while IFS= read line; do
echo "$line"
done < $f
echo
echo;
} > $OUTPUT
done