Skrypt Bash, aby powtórzyć każde słowo w linii?

13

Mam ciąg taki jak: dog cat bird whale

I chcę dostać dog dog cat cat bird bird whale whale

Wszystkie słowa są w tej samej linii. Dowolny pomysł?

Cristian
źródło

Odpowiedzi:

28

Dodanie do rodziny rozwiązań :-).

duplicator.sh:

for i; do echo -n "$i $i "; done; echo

Udostępnij plik wykonywalny, a teraz:

$ ./duplicator.sh dog cat bird whale
dog dog cat cat bird bird whale whale

Alternatywnie jako funkcja powłoki, np. W celu wielokrotnego użytku w skrypcie:

duplicator() {
    for i; do echo -n "$i $i "; done; echo
}

które można następnie uruchomić bezpośrednio tam, gdzie zdefiniowano jako

duplicator dog cat bird whale
Daniel Andersson
źródło
4
Podoba mi się prostota podejścia do powłoki.
Henk Langeveld,
Naprawdę uwielbiam prostotę tego rozwiązania
Cristian
O wiele lepiej niż na kilka pierwszych sposobów, o których myślałem.
Joe
21

Możesz użyć sed:

sed -r 's/(\S+)/\1 \1/g' filename

Jeśli chcesz zapisać zmiany w pliku w miejscu, powiedz:

sed -i -r 's/(\S+)/\1 \1/g' filename

Możesz także użyć perl:

perl -M5.10.0 -ne 'say join " ", map{$_, $_} split " ";' filename

(Dodaj -iopcję zapisania zmian w pliku w miejscu).

Lub, jak sugeruje terdon :

perl -M5.10.0 -ane 'say join " ", map{$_, $_} @F;' filename

Cytowanie z perlvar:

@F

Tablica @Fzawiera pola każdej linii wczytywane, gdy włączony jest tryb automatycznego podziału. Zobacz perlrun dla -aprzełącznika. Ta tablica jest specyficzna dla pakietu i musi być zadeklarowana lub otrzymać pełną nazwę pakietu, jeśli nie jest w pakiecie głównym, gdy jest uruchomiony strict 'vars'.

diabelnie
źródło
4
Krótszy: sed -r 's/\S+/& &/g'.
cyr
2
To nie jest skrypt Bash. Wywołanie jakiegoś polecenia (nawet z powłoki Bash) nie stanowi skryptu Bash.
Peter Mortensen
4
@PeterMortensen Jesteś technicznie poprawny (najlepszy rodzaj poprawek), ale praktycznie każdy system, który ma zainstalowany bash, będzie również miał standardowe narzędzia uniksowe, w tym sed i awk. Celem skryptów powłoki (cóż, znaczna jej część) jest wskazywanie poleceń we właściwym miejscu.
evilsoup
3
@PeterMortensen To jest niepoprawne. Skrypt bash może wywoływać polecenia zewnętrzne. Skrypt bash powinien zaczynać się od linii shebang, ale nie jest to absolutnie konieczne. W pytaniu nie określono, że skrypt bash nie powinien wywoływać zewnętrznych poleceń (często nazywanych „czystym skryptem bash”).
Gilles
2
Niezła sztuczka Perla. Możesz go skrócić za pomocą -a:perl -M5.10.0 -ane 'say join " ", map{$_, $_} @F;'
terdon
4

Co by to było bez awk/gawkodpowiedzi:

$ awk '{ for(i=1;i<=NF+1;i+=1/2) { printf("%s ",$i); }}' <<<"dog cat bird whale"
dog dog cat cat bird bird whale whale 

Jeśli końcowa nowa linia jest ważna:

$ awk '{ for(i=1;i<=NF+1;i+=1/2) { printf("%s ",$i); }} END{print ""}' <<<"dog cat bird whale"
użytkownik19087
źródło
Moje pierwsze zdanie. Ciekawe dlaczego? Czy coś jest nie tak ze skryptem? Lub bardziej skrócona wersja?
user19087
Nie moja opinia negatywna, ale for(i=1;i<=NF;++i) printf "%s %s ",$i,$i;jest zarówno krótsza, jak i bardziej czytelna, IMHO.
rici
Trudno się z tym kłócić, więc uprościłem i skróciłem swoją odpowiedź (teraz wykorzystuję zaokrąglanie indeksów do int) bez zmiany podejścia. Mam nadzieję, że jest teraz jaśniejsze.
user19087
1
To nie jest skrypt Bash. Wywołanie jakiegoś polecenia (nawet z powłoki Bash) nie stanowi skryptu Bash.
Peter Mortensen
Umieszczenie tego w skrypcie jest jednak trywialne.
Kevin
3
s="dog cat bird wale"
ss=$( tr ' ' '\n' <<< "$s" | sed p | tr '\n' ' ' )
echo "$ss"
dog dog cat cat bird bird wale wale 
Glenn Jackman
źródło
1
Myślałem o pisaniu sed -n 'p;p'- myślałem, że to jest bardziej przejrzyste o tym, co robi.
glenn jackman
1
Powinieneś dodać to do odpowiedzi!
terdon
1

Jeśli masz ciąg w zmiennej, powiedzmy foo="dog cat bird whale", możesz zrobić:

  • Pure Bash:

    $ echo "$foo" | (read a b c d && echo "$a $a $b $b $c $c $d $d")
    dog dog cat cat bird bird whale whale

    Objaśnienie: Nawiasy są potrzebne, aby readi echodziałały w tej samej podpowłoce, a zatem mogą dzielić zmienne. Bez nich po echoprostu wydrukowałaby pustą linię.

  • coreutils:

    $ join -j 5 -o 1.1,1.1,1.2,1.2,1.3,1.3,1.4,1.4 <(echo $foo) <(echo)
    dog dog cat cat bird bird whale whale

    Objaśnienie:-o Flaga joinpozwala ustawić format wyjściowy. Mówię tutaj, aby wydrukował 1. pole 1. pliku ( 1.1), a następnie 2. pole 1. pliku ( 1.2) itd. W ten sposób każde pole 1. pliku jest drukowane dwukrotnie. Jednak joinjest przeznaczony do łączenia dwóch linii wejściowych we wspólnym polu. Więc przekazuję mu również pustą linię ( <(echo)), a następnie go ignoruję. Te -jzestawy sprzężenia pola, ustawiając go na taki, który nie istnieje (5th) powoduje joinwydrukowanie całej linii.

    Jeśli nie obchodzi Cię spacja lub kolejność wprowadzania, możesz to zrobić

    $ paste <(echo $foo) <(echo $foo)
    dog cat bird wale   dog cat bird wale
  • Perl 1:

    $ echo $foo | perl -lane 'push @k, $_,$_ for @F; print "@k"'
    dog dog cat cat bird bird whale whale

    Wyjaśnienie:

    -l: adds a newline to each print call (among other things)
    -a: turns on field splitting, fields are saved as @F
    -n: process input line by line
    -e: give a script as a command line parameter.

    Powyższy skrypt zapisze każde pole (z @F) dwa razy w tablicy, @ka następnie wydrukuje @k. Jeśli nie potrzebujesz końcowego znaku nowej linii, możesz to uprościć

    $ echo $foo | perl -ane 'print " $_ $_" for @F'
  • Perl 2:

    $ echo $foo | perl -0040 -pne 'print "$_"' | paste - - 
    dog dog cat cat bird bird whale whale

    Objaśnienie:-0 opcja ustawia separator rekordu wejściowego (w systemie szesnastkowym lub liczby ósemkowej, patrz tutaj dla konwersji). Tutaj ustawiam go na ósemkę, 040która jest przestrzenią. Te -pmarki perlwydrukować każde wejście „line”, a ponieważ musimy ustawić separator rekordu do przestrzeni, linie są obecnie określone przez przestrzenie, więc każde pole jest drukowany dwukrotnie.

  • awk:

    $ echo $foo | awk '{for(i=1;i<=NF;i++){$i=$i" "$i;} 1;}'
    dog dog cat cat bird bird whale whale

    Objaśnienie: NF jest liczbą pól, więc powyższy skrypt przechodzi przez każde pole i dołącza je do siebie. Gdy to zrobisz, drukujemy linię ( 1;jest po prostu skrótem do wydruku).

terdon
źródło
0

Teraz pythonodpowiedź:

Z wiersza poleceń:

$ python -c "import sys; s=sys.argv[1:]; print(' '.join(j for i in zip(s,s)for j in i));" dog cat bird whale

Od standardowego:

$ python -c "s=input().split(); print(' '.join(j for i in zip(s,s)for j in i));" <<<"dog cat bird whale"

Wynik w obu przypadkach:

dog dog cat cat bird bird whale whale
użytkownik19087
źródło
0

Nieco przesadzone, ale haskellodpowiedź:

$ ghc -e "getLine >>= putStrLn . unwords . (concatMap $ replicate 2) . words" <<<"dog cat bird whale"
dog dog cat cat bird bird whale whale
użytkownik19087
źródło
0

Inne podejście, wykorzystujące tylko wbudowane bash

$ string="dog cat bird whale"
$ twix() { while [[ ! -z $1 ]]; do printf "%s %s " $1 $1; shift; done; }
$ twix $string
dog dog cat cat bird bird whale whale

Nie widzę żadnych korzyści w porównaniu z najlepszą odpowiedzią, aby pokazać nieco inny sposób, który może być bardziej odpowiedni do niektórych celów.

mpy
źródło
echojest także powłoką wbudowaną w Bash (test type echo).
Daniel Andersson,
@DanielAndersson: Jasne, dlatego napisałem „ także używając tylko wbudowanych wersji bash”, mając na uwadze twoją miłą (już +1) odpowiedź.
mpy
OK, możesz sklasyfikować mój komentarz jako zamieszanie językowe :-).
Daniel Andersson