Bir komuttaki bir dosyayı nasıl kullanabilirim ve çıktıyı kesmeden aynı dosyaya yeniden yönlendirebilirim?


98

Temel olarak bir dosyadan girdi metni olarak almak, o dosyadan bir satırı kaldırmak ve çıktıyı aynı dosyaya geri göndermek istiyorum. Bu çizgide bir şey, eğer bu onu daha açık hale getiriyorsa.

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

ancak, bunu yaptığımda boş bir dosya ile karşılaşıyorum. Düşüncesi olan var mı?


Yanıtlar:


86

Bunu yapamazsınız çünkü bash önce yönlendirmeleri işler, sonra komutu çalıştırır. Yani grep dosya_adına baktığında zaten boştur. Yine de geçici bir dosya kullanabilirsiniz.

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

bunun gibi mktemp, tmpfile'ı oluşturmak için kullanmayı düşünün , ancak POSIX olmadığını unutmayın.


47
Bunu yapamamanızın nedeni: bash önce yönlendirmeleri işler, sonra komutu çalıştırır. Yani grep dosya_adına baktığında zaten boştur.
glenn jackman

1
@glennjackman: "yeniden yönlendirme işlemlerinden kastınız,> durumunda dosyayı açıp temizler ve >> durumunda sadece açar"?
Razvan

2
evet, ancak bu durumda, >yeniden yönlendirme dosyayı açacak ve kabuk başlamadan önce onu kesecektir grep.
glenn jackman

1
Bkz Cevabımı geçici bir dosyayı kullanmayı istemiyorsanız, ancak bu yorumu upvote etmeyiniz.
Zack Morris

Bunun yerine komutu kullanan cevapsponge kabul edilmelidir.
vlz

99

Bu tür işler için sünger kullanın . Moreutils'in bir parçası.

Bu komutu deneyin:

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

4
Cevap için teşekkürler. Muhtemelen yararlı bir ek olarak, Mac'te homebrew kullanıyorsanız, kullanabilirsiniz brew install moreutils.
Anthony Panozzo

2
Veya sudo apt-get install moreutilsDebian tabanlı sistemlerde.
Jonah

3
Lanet olsun! Beni moreutils =) orada bazı güzel programlar ile tanıştırdığınız için teşekkürler!
netigger

çok teşekkür ederim, kurtarma için daha çok kazanç! patron gibi sünger!
aqquadro

3
Dikkatli bir kelime, "sünger" yıkıcıdır, bu nedenle komutunuzda bir hata varsa, girdi dosyanızı silebilirsiniz (süngeri ilk kez denediğimde yaptığım gibi). Komutun çalışmasını sağlamak için yinelemeye çalışıyorsanız, komutunuzun çalıştığından ve / veya giriş dosyasının sürüm kontrolü altında olduğundan emin olun.
user107172

19

Bunun yerine sed kullanın:

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

1
iirc -i, sadece GNU'nun uzantısıdır.
c00kiemon5ter

4
* BSD'de (ve dolayısıyla OSX'te) -i '', uzantının kesinlikle zorunlu olmadığını söyleyebilirsiniz , ancak -iseçenek bazı argüman gerektirir .
2017

15

bu basit olanı dene

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

Dosyanız bu sefer boş olmayacak :) ve çıktınız da terminalinize yazdırılacaktır.


1
Bu çözümü beğendim! Ve eğer terminalde yazdırılmasını istemiyorsanız, yine de çıktıyı /dev/nullveya benzer yerlere yönlendirebilirsiniz .
Frozn

4
Bu, dosya içeriğini burada da temizler. Bu bir GNU / BSD farkından mı kaynaklanıyor? MacOS'tayım ...
ssc


7

Aynı dosyaya yeniden yönlendirme operatörü ( >veya >>) kullanamazsınız çünkü daha yüksek bir önceliğe sahiptir ve komut çağrılmadan önce dosyayı oluşturur / keser. Bunu önlemek için, gibi uygun araçları kullanmak gerekir tee,sponge , sed -iveya dosya (örn sonuçları yazabilirsiniz başka bir araç sort file -o file).

Temel olarak girdiyi aynı orijinal dosyaya yeniden yönlendirmek mantıklı değildir ve bunun için uygun yerinde düzenleyiciler kullanmalısınız, örneğin Ex düzenleyici (Vim'in bir parçası):

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

nerede:

  • '+cmd'/-c - herhangi bir Ex / Vim komutunu çalıştırın
  • g/pattern/d- global kullanarak bir desenle eşleşen çizgileri kaldırın ( help :g)
  • -s - sessiz mod (man ex )
  • -c wq- çalıştır :writeve :quitkomutlar

Sen kullanabilir sedancak, (zaten başka yanıtlar gösterildiği gibi) aynı elde etmek yerinde ( -i(Unix / Linux arasındaki farklı çalışabilir) standart dışı FreeBSD uzantısıdır) ve temelde bir olduğunu s Team, ed in- değil, bir dosya editörü . Bakınız: Ex modunun herhangi bir pratik kullanımı var mı?


6

Tek satırlık alternatif - dosyanın içeriğini değişken olarak ayarlayın:

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

4

Bu soru arama motorlarında en iyi sonuç olduğundan, burada bir alt kabuk kullanan (genellikle OS X gibi bir vanilya kurulumunun parçası olmayan) https://serverfault.com/a/547331 temelli tek satırlık spongebir program var. :

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

Genel durum şudur:

echo "$(cat file_name)" > file_name

Düzenleyin, yukarıdaki çözümün bazı uyarıları var:

  • printf '%s' <string>echo <string>içeren dosyaların -nistenmeyen davranışlara neden olmaması için bunun yerine kullanılmalıdır .
  • Yeni satır sondaki Komut ikamesi şeritleri ( bu bash gibi kabukları bir hata / özelliğidir ) biz böyle bir sonek karakterini eklemek gerekir böylece xçıkışa ve üzeri dışarıda çıkarın geçici değişkenin parametre genişlemesi gibi${v%x} .
  • Geçici bir değişken kullanmak , mevcut kabuk ortamındaki $vmevcut herhangi bir değişkenin değerini bastırır $v, bu nedenle önceki değeri korumak için tüm ifadeyi parantez içine yerleştirmeliyiz.
  • Bash gibi kabukların bir başka hatası / özelliği, komut ikamesinin, gibi yazdırılamayan karakterleri çıkarmasıdır. null çıktıdaki . Bunu dd if=/dev/zero bs=1 count=1 >> file_nameonaltılık olarak arayarak ve görüntüleyerek doğruladım cat file_name | xxd -p. Ama echo $(cat file_name) | xxd -pelimden alındı. Yani bu cevabı gerektiğini değil olarak, basılamaz karakterler kullanarak ikili dosyaları veya şey üzerinde kullanılabilir Lynch işaret .

Genel çözüm (biraz daha yavaş, daha fazla bellek yoğun ve yine de yazdırılamayan karakterleri çıkarıyor):

(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)

Https://askubuntu.com/a/752451 adresinden test edin :

printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do (v=$(cat file_uniquely_named.txt; printf x); printf '%s' ${v%x} > file_uniquely_named.txt); done; cat file_uniquely_named.txt; rm file_uniquely_named.txt

Yazdırmalı:

hello
world

cat file_uniquely_named.txt > file_uniquely_named.txtMevcut kabukta arama yaparken:

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

Boş bir dize yazdırır.

Bunu büyük dosyalarda test etmedim (muhtemelen 2 veya 4 GB'nin üzerinde).

Bu cevabı Hart Simha ve kos'tan ödünç aldım .


2
Tabii ki büyük bir dosya ile çalışmayacaktır. Bu muhtemelen iyi bir çözüm olamaz veya her zaman işe yaramaz. Olan şey, bash'ın önce komutu çalıştırması ve ardından stdout'u yükleyip catilk argüman olarak koymasıdır echo. Elbette yazdırılamayan değişkenler doğru şekilde çıktı vermez ve verileri bozmaz. Bir dosyayı kendisine geri yönlendirmeye çalışmayın, bu iyi olamaz.
Lynch

1

Ayrıca ed(alternatif olarak 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

1

Bunu işlem ikamesi kullanarak yapabilirsiniz .

Bash tüm boruları eşzamansız olarak açtığından ve sleepYMMV'yi kullanarak bunun üzerinde çalışmamız gerektiğinden, bu biraz hack'lendi.

Örneğinizde:

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name)
  • >(sleep 1 && cat > file_name) grep'ten çıktı alan geçici bir dosya oluşturur
  • sleep 1 girdi dosyasını ayrıştırmak için grep süresi vermek için bir saniyelik gecikmeler
  • sonunda cat > file_nameçıktıyı yazar

1

Slurp'u POSIX Awk ile kullanabilirsiniz:

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

Misal


1
"Slurp" un "tüm dosyayı hafızaya oku" anlamına geldiğini belirtmek gerekir. Büyük bir girdi dosyanız varsa, bundan kaçınmak isteyebilirsiniz.
üçlü

1

Bu çok mümkün, sadece çıktıyı yazdığınız anda onu farklı bir dosyaya yazdığınızdan emin olmalısınız. Bu, bir dosya tanımlayıcısını açtıktan sonra, ancak ona yazmadan önce dosyayı kaldırarak yapılabilir:

exec 3<file ; rm file; COMMAND <&3 >file ;  exec 3>&-

Veya daha iyi anlamak için satır satır:

exec 3<file       # open a file descriptor reading 'file'
rm file           # remove file (but fd3 will still point to the removed file)
COMMAND <&3 >file # run command, with the removed file as input
exec 3>&-         # close the file descriptor

Yine de yapılması riskli bir şey, çünkü COMMAND düzgün çalışmazsa dosya içeriğini kaybedersiniz. COMMAND sıfırdan farklı bir çıkış kodu döndürürse dosyayı geri yükleyerek bu durum hafifletilebilir:

exec 3<file ; rm file; COMMAND <&3 >file || cat <&3 >file ; exec 3>&-

Kullanımı daha kolay hale getirmek için bir kabuk işlevi de tanımlayabiliriz:

# Usage: replace FILE COMMAND
replace() { exec 3<$1 ; rm $1; ${@:2} <&3 >$1 || cat <&3 >$1 ; exec 3>&- }

Misal :

$ echo aaa > test
$ replace test tr a b
$ cat test
bbb

Ayrıca, bunun orijinal dosyanın tam bir kopyasını saklayacağını unutmayın (üçüncü dosya tanımlayıcı kapanana kadar). Linux kullanıyorsanız ve üzerinde işlem yaptığınız dosya diske iki kez sığmayacak kadar büyükse , önceden işlenmiş olanın ayırmasını kaldırırken, dosyayı belirtilen komuta bloklar halinde yönlendirecek bu komut dosyasını kontrol edebilirsiniz. bloklar. Her zaman olduğu gibi kullanım sayfasındaki uyarıları okuyun.


0

Bunu dene

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

cat testfile
AAA
BBB
CCC

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

Kısa bir açıklama veya hatta yorumlar yardımcı olabilir.
Rich

bence işe yarıyor çünkü dize ekstrapolasyonu yönlendirme operatöründen önce çalışıyor, ancak tam olarak bilmiyorum
Виктор Пупкин

0

Aşağıdakiler, bununla aynı şeyi spongegerektirmeden başaracaktır moreutils:

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

--random-source=/dev/zerobölüm hilelershuf hiç bir üreticimizin yapmadan olan şey yapıyor içine, bu yüzden onu değiştirmeden girişi tamponlayacaktır.

Ancak, performans nedenleriyle geçici bir dosya kullanmanın en iyisi olduğu doğrudur. İşte bunu sizin için genelleştirilmiş bir şekilde yapacak yazdığım bir işlev:

# 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"
}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.