web-dev-qa-db-fra.com

Comment puis-je utiliser un fichier dans une commande et rediriger la sortie vers le même fichier sans le tronquer?

Fondamentalement, je veux prendre comme texte d'entrée d'un fichier, supprimer une ligne de ce fichier et renvoyer la sortie dans le même fichier. Quelque chose dans ce sens si cela le rend plus clair.

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name

cependant, lorsque je fais cela, je me retrouve avec un fichier vierge. Des pensées?

81
mike

Vous ne pouvez pas faire cela car bash traite d'abord les redirections, puis exécute la commande. Donc, au moment où grep regarde file_name, il est déjà vide. Vous pouvez cependant utiliser un fichier temporaire.

#!/bin/sh
tmpfile=$(mktemp)
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > ${tmpfile}
cat ${tmpfile} > file_name
rm -f ${tmpfile}

comme ça, pensez à utiliser mktemp pour créer le tmpfile mais notez que ce n'est pas POSIX.

75
c00kiemon5ter

Utilisez éponge pour ce type de tâches. Sa partie de moreutils.

Essayez cette commande:

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name
77
Lynch

Utilisez plutôt sed:

sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name
17
Manny D

essayez ce simple

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name

Votre fichier ne sera pas vierge cette fois :) et votre sortie est également imprimée sur votre terminal.

9
sailesh ramanam

Vous ne pouvez pas utiliser l'opérateur de redirection (> ou >>) dans le même fichier, car il a une priorité plus élevée et il créera/tronquera le fichier avant même que la commande soit invoquée. Pour éviter cela, vous devez utiliser les outils appropriés tels que tee, sponge, sed -i ou tout autre outil pouvant écrire des résultats dans le fichier (par exemple sort file -o file).

Fondamentalement, la redirection de l'entrée vers le même fichier d'origine n'a pas de sens et vous devez utiliser des éditeurs sur place appropriés pour cela, par exemple l'éditeur Ex (qui fait partie de Vim):

ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name

où:

  • '+cmd'/-c - exécutez n'importe quelle commande Ex/Vim
  • g/pattern/d - supprime les lignes correspondant à un modèle à l'aide de global (help :g)
  • -s - mode silencieux (man ex)
  • -c wq - exécuter :write et :quit commandes

Vous pouvez utiliser sed pour obtenir la même chose (comme déjà indiqué dans d'autres réponses), cependant en place (-i) est une extension FreeBSD non standard (peut fonctionner différemment entre Unix/Linux) et, fondamentalement, c'est un s tream ed itor, pas un éditeur de fichiers. Voir: Le mode Ex a-t-il une utilité pratique?

8
kenorb

Une alternative liner - définissez le contenu du fichier comme variable:

VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name
5
w00t

Puisque cette question est le meilleur résultat dans les moteurs de recherche, voici un one-liner de https://serverfault.com/a/547331 qui utilise un sous-shell au lieu de sponge (qui souvent ne fait pas partie d'une installation Vanilla comme OS X):

echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name

Ou le cas général:

echo "$(cat file_name)" > file_name

Testez à partir de https://askubuntu.com/a/752451 :

printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do echo "$(cat file_uniquely_named.txt)" > file_uniquely_named.txt; done; cat file_uniquely_named.txt; rm file_uniquely_named.txt

Devrait imprimer:

hello
world

En appelant cat file_uniquely_named.txt > file_uniquely_named.txt dans le shell actuel:

printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do cat file_uniquely_named.txt > file_uniquely_named.txt; done; cat file_uniquely_named.txt; rm file_uniquely_named.txt

Imprime une chaîne vide.

Je n'ai pas testé cela sur des fichiers volumineux (probablement plus de 2 ou 4 Go).

J'ai emprunté cette réponse à Hart Simha et kos .

3
Zack Morris

Vous pouvez utiliser Slurp avec POSIX Awk:

!/seg[0-9]\{1,\}\.[0-9]\{1\}/ {
  q = q ? q RS $0 : $0
}
END {
  print q > ARGV[1]
}

Exemple

2
Steven Penny

Vous pouvez le faire en utilisant substitution de processus .

C'est un peu un hack bien que bash ouvre tous les canaux de manière asynchrone et nous devons contourner cela en utilisant sleep donc YMMV.

Dans votre exemple:

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name)
  • >(sleep 1 && cat > file_name) crée un fichier temporaire qui reçoit la sortie de grep
  • sleep 1 Attend une seconde pour donner à grep le temps d'analyser le fichier d'entrée
  • enfin cat > file_name écrit la sortie
2
laktak

Il y a aussi ed (comme alternative à sed -i):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' H 'g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' wq |  ed -s file_name
2
nerx

Essaye ça

echo -e "AAA\nBBB\nCCC" > testfile

cat testfile
AAA
BBB
CCC

echo "$(grep -v 'AAA' testfile)" > testfile
cat testfile
BBB
CCC

J'utilise habituellement le programme tee pour ce faire:

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name

Il crée et supprime un fichier temporaire par lui-même.

1
Carlos Fanelli

Ce qui suit accomplira la même chose que sponge, sans nécessiter moreutils:

    shuf --output=file --random-source=/dev/zero 

Le --random-source=/dev/zero partie astuces shuf pour faire son travail sans faire de mélange du tout, donc il tamponnera votre entrée sans la modifier.

Cependant, il est vrai que l'utilisation d'un fichier temporaire est préférable, pour des raisons de performances. Donc, voici une fonction que j'ai écrite qui fera cela pour vous de manière généralisée:

# Pipes a file into a command, and pipes the output of that command
# back into the same file, ensuring that the file is not truncated.
# Parameters:
#    $1: the file.
#    $2: the command. (With $3... being its arguments.)
# See https://stackoverflow.com/a/55655338/773113

function siphon
{
    local tmp=$(mktemp)
    local file="$1"
    shift
    $* < "$file" > "$tmp"
    mv "$tmp" "$file"
}
0
Mike Nakis